package function.builtin;

import static function.builtin.EVAL.eval;

import java.io.*;
import java.text.MessageFormat;

import environment.RuntimeEnvironment;
import error.*;
import function.*;
import parser.LispParser;
import sexpression.*;

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

    private ArgumentValidator argumentValidator;
    private ErrorManager errorManager;

    public LOAD() {
        this.argumentValidator = new ArgumentValidator("LOAD");
        this.argumentValidator.setExactNumberOfArguments(1);
        this.argumentValidator.setEveryArgumentExpectedType(LispString.class);
        this.errorManager = RuntimeEnvironment.getInstance().getErrorManager();
    }

    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 wasSuccessful = false;
        LispParser parser = attemptToCreateParserOnFile(fileName);

        if (parser != null)
            wasSuccessful = isSuccessfulEvaluation(parser);

        return wasSuccessful ? Symbol.T : Nil.getInstance();
    }

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

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

        return parser;
    }

    private boolean isSuccessfulEvaluation(LispParser parser) {
        while (!parser.isEof()) {
            try {
                eval(parser.getNextSExpression());
            } catch (LispException e) {
                errorManager.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 MessageFormat.format("could not load ''{0}''", fileName);
        }
    }

}
