package function.builtin;

import static function.builtin.EVAL.eval;
import static java.text.MessageFormat.format;
import static sexpression.Nil.NIL;
import static sexpression.Symbol.T;
import static util.Path.getPathPrefix;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Stack;

import environment.RuntimeEnvironment;
import error.LispException;
import error.LispWarning;
import function.ArgumentValidator;
import function.FunctionNames;
import function.LispFunction;
import parser.LispParser;
import sexpression.Cons;
import sexpression.LispString;
import sexpression.SExpression;

@FunctionNames({ "LOAD" })
public class LOAD extends LispFunction {

    private static Stack<String> pathPrefixes = new Stack<>();
    private ArgumentValidator argumentValidator;
    private RuntimeEnvironment environment;

    public LOAD(String name) {
        this.argumentValidator = new ArgumentValidator(name);
        this.argumentValidator.setExactNumberOfArguments(1);
        this.argumentValidator.setEveryArgumentExpectedType(LispString.class);
        this.environment = RuntimeEnvironment.getInstance();
    }

    @Override
    public SExpression call(Cons argumentList) {
        argumentValidator.validate(argumentList);

        LispString quotedName = (LispString) argumentList.getFirst();
        String fileName = removeSurroundingQuotes(quotedName.toString());

        return processFile(fileName);
    }

    private String removeSurroundingQuotes(String fileName) {
        return fileName.substring(1, (fileName.length() - 1));
    }

    private SExpression processFile(String fileName) {
        boolean isSuccessful = false;
        String prefixedFileName = prefixFileNameIfNecessary(fileName);
        LispParser parser = attemptToCreateParserOnFile(prefixedFileName);

        if (parser != null)
            isSuccessful = isSuccessfulEvaluationWithPathPrefix(prefixedFileName, parser);

        return isSuccessful ? T : NIL;
    }

    private String prefixFileNameIfNecessary(String fileName) {
        if (pathPrefixes.isEmpty())
            return environment.getPath() + fileName;

        return pathPrefixes.peek() + fileName;
    }

    private LispParser attemptToCreateParserOnFile(String fileName) {
        LispParser parser = null;

        try {
            parser = new LispParser(new FileInputStream(fileName), fileName);
        } catch (FileNotFoundException e) {
            environment.getErrorManager().handle(new CouldNotLoadFileWarning(fileName));
        }

        return parser;
    }

    private boolean isSuccessfulEvaluationWithPathPrefix(String prefixedFileName, LispParser parser) {
        pathPrefixes.push(getPathPrefix(prefixedFileName));
        boolean isSuccessful = isSuccessfulEvaluation(parser);
        pathPrefixes.pop();

        return isSuccessful;
    }

    private boolean isSuccessfulEvaluation(LispParser parser) {
        while (!parser.isEof()) {
            try {
                eval(parser.getNextSExpression());
            } catch (LispException e) {
                environment.getErrorManager().handle(e);
                return false;
            }
        }

        return true;
    }

    public static class CouldNotLoadFileWarning extends LispWarning {

        private static final long serialVersionUID = 1L;
        private String fileName;

        public CouldNotLoadFileWarning(String fileName) {
            this.fileName = fileName;
        }

        @Override
        public String getMessage() {
            return format("could not load ''{0}''", fileName);
        }
    }

}
