package code.lang;

import code.instr.*;
import code.modules.CurryModule;
import code.space.Computation;
import code.space.Space;
import code.stuff.Logger;
import code.stuff.Tracer;
import code.stuff.Util;
import code.symbols.*;
import code.table.ModuleTable;
import code.term.Term;
import code.term.TermImplChar;
import code.type.*;
import static code.type.PredefinedTypes.*;
import static code.type.TypeFactory.*;

import java.io.*;
import java.util.Vector;

/**
 * @author Marius Nita
 * @author Pravin DAmle
 * @since 7/11/2004
 */
public class IOModule extends CurryModule {
    // public static final String moduleName = "$IO";
    public static final String moduleName = "Prelude";

    private static final TypeSymbol ioTypeSymbol =
            new TypeSymbol(moduleName, ioTypeName, 1, Symbol.Public);

    public IOModule() {
        super(moduleName, new Vector(), null, 0, createSymbols(), false);
    }

    private static Vector createSymbols() {
        Vector v = new Vector();
        v.add(ioTypeSymbol);
        v.add(auxBindOp);
        v.add(returnOperation);
        v.add(auxCatchFailOp);
        v.add(catchFailOperation);
        v.add(bindOperation);
        v.add(getCharOperation);
        v.add(putCharOperation);

        return v;
    }

    private static int unitID = ModuleTable.getId("Prelude","()");
    private static int applyID =
        ModuleTable.getId(SystemModule.moduleName,SystemModule.applyName);
    private static final int tupleID =
        ModuleTable.getId(TupleModule.moduleName,
                          TupleModule.getConstructorName(2));

    public static TypeExpression mkIOType(TypeExpression tArg) {
        return makeApplicationType(ioTypeName,tArg);
    }

    // ------------------------------------------------------------------
    // TODO: bind and return can be written as Curry functions:
    //
    //    return :: a -> IO a
    //    return v world = (v,world)
    //
    //    (>>=) :: IO a -> (a -> IO b) -> IO b
    //    (>>=) action rest world =
    //      case action world of
    //        (result,world') -> rest result world'
    //
    // where (IO a) would be a synonym for World -> (a,World)
    //
    // It is conceivable that one could maintain these functions as
    // Curry code and compile them, instead of keeping them in the
    // machine.  $Support has been suggested as such a place, but it is
    // unclear how exactly to put all the plumbing in place.
    // ------------------------------------------------------------------

    // ------------------------------------------------------------------
    // Operation return :: a -> IO a
    // ------------------------------------------------------------------
    private static final String returnName = "return";
    private final static TypeExpression returnType
	= makeFunctionType
            (makeTypeVariable("a"),mkIOType(makeTypeVariable("a")));
    private static Instruction[] returnCode = {
        new Load((byte)1),
        Push.singleton,
        new Load((byte)0),
        Push.singleton,
        new MakeTerm(TupleModule.moduleName,TupleModule.getConstructorName(2)), 
        Pop.singleton,
        Replace.singleton,
    };
    public final static OperationSymbol returnOperation
        = new OperationSymbol(moduleName,
                              returnName,
                              DataSymbol.toplevel,
                              2,
                              DataSymbol.NonInfix,
                              DataSymbol.NonInfix,
                              Symbol.Public,
                              // a -> IO a
                              returnType,
                              returnCode);

    // ------------------------------------------------------------------
    // Operation catchFail :: IO a -> IO a -> IO a
    // ------------------------------------------------------------------
    private static final String auxCatchFailOpName = "#io_auxCatchFail";
    private static final OperationSymbol auxCatchFailOp =
        new OperationSymbol(moduleName,
                            auxCatchFailOpName,
                            DataSymbol.toplevel,
                            3,
                            DataSymbol.NonInfix,
                            DataSymbol.NonInfix,
                            Symbol.Private,
                            // b -> IO a -> IO a -- bogus
                            makeFunctionType
                            (makeTypeVariable("b"),
                             makeFunctionType
                             (mkIOType(makeTypeVariable("a")),
                              mkIOType(makeTypeVariable("a")))),
                            new Instruction[] {
                                new auxCatchFailInstruction(),
                            });

    private static class auxCatchFailInstruction implements Instruction {
        public void execute(Computation computation) {
            Term term = computation.getTerm();
            // left should now be evaluated to a pair (value,world)
            Term left = term.getArgument((byte) 0);
            Term right = term.getArgument((byte) 1);
            Term world = term.getArgument((byte) 2);
            switch (left.getKind()) {
                case DataSymbol.UnboundVariable:
                    computation.selfSetState(Computation.RESIDUATING);
                    break;
                case DataSymbol.Failure:
                    //computation.push(right);
                    term.update(new Term(applyID,new Term[]{right,world}));
                    break ;
                case DataSymbol.Operation:
                    computation.push(left);
                    break;
                default: // term is a normal form
                    if (Tracer.reduction)
			Tracer.traceRewrite(computation, term, left);
                    term.update(left);
                    break;
            }
            return;
        }
        public String printAsTxtLoadable() {
            return "external function \"#io_auxCatchFail\"";
        }
    }

    private static final String catchFailName = "catchFail";
    private static final OperationSymbol catchFailOperation =
        new OperationSymbol(moduleName,
                            catchFailName,
                            DataSymbol.toplevel,
                            3,
                            DataSymbol.NonInfix,
                            DataSymbol.NonInfix,
                            Symbol.Public,
                            // IO a -> IO a -> IO a
                            makeFunctionType
                            (mkIOType(makeTypeVariable("a")),
                             makeFunctionType
                             (mkIOType(makeTypeVariable("a")),
                              mkIOType(makeTypeVariable("a")))),
                            new Instruction[]{
                                new Load((byte)2),
                                Push.singleton,
                                new Load((byte)1),
                                Push.singleton,
                                new Load((byte)2),
                                Push.singleton,
                                new Load((byte)0),
                                Push.singleton,
                                new MakeTerm(SystemModule.moduleName,SystemModule.applyName),
                                new MakeTerm(moduleName,auxCatchFailOpName),
                                Pop.singleton,
                                Replace.singleton
                            });

    // ------------------------------------------------------------------
    // getChar :: IO Char
    // ------------------------------------------------------------------
    private final static DataSymbol getCharOperation =
            new OperationSymbol(moduleName,
                    "getChar",
                    DataSymbol.toplevel,
                    1,
                    DataSymbol.NonInfix,
                    DataSymbol.NonInfix,
                    Symbol.Public,
                    // IO Char
                    mkIOType(charType),
                    new Instruction[]{
                        new GetCharInstruction(),
                    });

    private static class GetCharInstruction implements Instruction {
	public void execute(Computation computation) {
	    Term term = computation.getTerm();
	    Term reduct;
	    try {
		int inputChar = System.in.read();
		if (inputChar == -1) {
		    // EOF condition. so fail the computation. this is not an IO Exception.
		    reduct = SuccessModule.termFail ;
		} else {
		    // return the value of inputChar as IO Char
		    char charValue = (char) inputChar;
                    // build (charValue,world)
		    Term[] tmp = new Term[]{new Term(charValue), term.getArgument((byte)0)};
		    reduct = new Term(tupleID, tmp);
		}
	    } catch (IOException e) {
		reduct = SuccessModule.termFail ;
	    }
	    if (Tracer.reduction)
		Tracer.traceRewrite(computation, term, reduct);
	    term.update(reduct);
	}
	public String printAsTxtLoadable() {
	    return "external function \"getChar\"";
	}
    }

    // ------------------------------------------------------------------
    // putChar :: Char -> IO ()
    // ------------------------------------------------------------------   
    private final static DataSymbol putCharOperation =
	new OperationSymbol(moduleName,
			    "putChar",
			    DataSymbol.toplevel,
			    2,
			    DataSymbol.NonInfix,
			    DataSymbol.NonInfix,
			    Symbol.Public,
			    // Char -> IO ()
			    makeFunctionType(charType,mkIOType(unitType)),
			    new Instruction[]{
				new PutCharInstruction(),
			    });

    private static class PutCharInstruction implements Instruction {
	public void execute(Computation computation) {
	    // first arg of term is of the form char.
	    Term term = computation.getTerm();
	    Term argument = term.getArgument((byte) 0);
            switch (argument.getKind()) {
                case DataSymbol.UnboundVariable:
                    computation.selfSetState(Computation.RESIDUATING);
                    break;
                case DataSymbol.Failure:
		    term.update(SuccessModule.termFail);
                    break ;
                case DataSymbol.Operation:
                    computation.push(argument);
                    break;
                default: // term is a normal form, hence a character
                    Term world = term.getArgument((byte) 1);
		    char charValue 
			= ((TermImplChar) argument.getRepresentation()).value;
                    Term result = new Term(tupleID, new Term[]{new Term(unitID, new Term[0]), world});
		    // HERE WE DO THE OUTPUT
		    System.out.print(charValue);
		    if (Tracer.reduction)
			Tracer.traceRewrite(computation, term, result);
		    term.update(result);
            }
	}
	public String printAsTxtLoadable() {
	    return "external function \"putChar\"";
	}
    }

    // ------------------------------------------------------------------
    // (>>=) :: IO a -> (a -> IO b) -> IO b
    // ------------------------------------------------------------------

    // this auxiliary op is needed to implement bind in bytecode.
    // no way around it.
    private static final String auxBindOpName = "#io_auxBind";
    private static final OperationSymbol auxBindOp =
        new OperationSymbol(moduleName,
                            auxBindOpName,
                            DataSymbol.toplevel,
                            2,
                            DataSymbol.NonInfix,
                            DataSymbol.NonInfix,
                            Symbol.Public,
                            // a -> b -> c
                            makeFunctionType(makeTypeVariable("a"),
                                             makeFunctionType(makeTypeVariable("b"),
                                                              makeTypeVariable("c"))),
                            new Instruction[]{
                                new Load((byte)1),
                                new Branch
                                  (new Instruction[][] {
                                      new Instruction[]{Residuate.singleton},
                                      new Instruction[]{Fail.singleton},
                                      new Instruction[]{
                                          new Load(new byte[]{1,1}),
                                          Push.singleton,
                                          new Load(new byte[]{1,0}),
                                          Push.singleton,
                                          new Load((byte)0),
                                          Push.singleton,
                                          new MakeTerm(SystemModule.moduleName,SystemModule.applyName),
                                          new MakeTerm(SystemModule.moduleName,SystemModule.applyName),
                                          Pop.singleton,
                                          Replace.singleton,
                                      }})});

    private final static DataSymbol bindOperation =
	new OperationSymbol(moduleName,
			    ">>=",
			    DataSymbol.toplevel,
			    3,
			    DataSymbol.NonInfix,
			    DataSymbol.NonInfix,
			    Symbol.Public,
			    // IO a -> (a -> IO b) -> IO b
                            makeFunctionType
                            (mkIOType(makeTypeVariable("a")),
                             makeFunctionType
                             (makeFunctionType(makeTypeVariable("a"), mkIOType(makeTypeVariable("b"))),
                              mkIOType(makeTypeVariable("b")))),
			    new Instruction[]{
                                new Load((byte)2),
                                Push.singleton,
                                new Load((byte)0),
                                Push.singleton,
                                new MakeTerm(SystemModule.moduleName,SystemModule.applyName),
                                new Load((byte)1),
                                Push.singleton,
                                new MakeTerm(moduleName,auxBindOpName),
                                Pop.singleton,
                                Replace.singleton
                            });
}
