package code.lang;

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

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

/**
 * @author Sergio Antoy
 * @since 3/30/05
 *
 * Upgraded to varargs on 4/12/05
 *
 * Change writeFile approach on Wed Apr 20 12:39:09 PDT 2005
 * because it could not write large (> 10,000 chars) files.
 * Replace instruction is dangerous for maintenance and it should 
 * be removed.
 * File names should be normalized here.
 */
public class IOauxModule extends CurryModule {
    public static final String moduleName = "$IO_aux";

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

    private static Vector createSymbols() {
        Vector v = new Vector();
	v.add(appendToStreamOperation);
        v.add(readFileOperation);
        v.add(writeFileOperation);
        v.add(appendFileOperation);
        return v;
    }

    // ------------------------------------------------------------------

    public static final String appendToStreamName = "appendToStream";
    private static final int appendToStreamID 
	= ModuleTable.getId(moduleName, appendToStreamName);

    private static int nilID = ModuleTable.getId("Prelude", "[]");
    private static int consID = ModuleTable.getId("Prelude", ":");
    private static Term nilTerm = new Term(nilID, new Term[]{});

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

    // ------------------------------------------------------------------

    private static class appendToStreamInstruction
     implements Instruction {
        public void execute(Computation computation) {
            Term term = computation.getTerm();
	    Term data = term.getArgument((byte) 1);
            Term world = term.getArgument((byte) 2);
	    Term reduct;
	    switch (data.getKind()) {
	    case DataSymbol.Operation:
		computation.push(data);
		return;
	    case DataSymbol.Failure:
		reduct = SuccessModule.termFail;
                if (Tracer.reduction)
		    Tracer.traceRewrite(computation, term, reduct);
		term.update(reduct);
		return;
	    default:
		// this is the tricky part
		Term stream = term.getArgument((byte) 0);
		TermImplOpaque impl = (TermImplOpaque) stream.getRepresentation();
		BufferedWriter br = (BufferedWriter) impl.opaque;
		int root = data.getRoot();
		try {
		    // If a string is evaluated already
		    // write as many characters as possible.
		    // TODO:  is this really an optimization?
		    while (root == consID) {
			Term head = data.getArgument((byte) 0);
			switch (head.getKind()) {
			case DataSymbol.Operation:
			    reduct =  new Term(appendToStreamID,
					       new Term [] { stream, data, world });
			    if (Tracer.reduction)
				Tracer.traceRewrite(computation, term, reduct);
			    term.update(reduct);
			    computation.push(head);
			    return;
			case DataSymbol.Failure:
			    reduct = SuccessModule.termFail;
			    if (Tracer.reduction)
				Tracer.traceRewrite(computation, term, reduct);
			    term.update(reduct);
			    return;
			default:
			    char charValue = ((TermImplChar) head.getRepresentation())
				.value;
			    br.append(charValue);
			    data = data.getArgument((byte) 1);
			    root = data.getRoot();
			}
		    }
		    if (root == nilID) {
			br.flush();
			br.close();
			reduct = new Term(tupleID, new Term[]{new Term(unitID,new Term[0]), world});
		    } else {
			reduct =  new Term(appendToStreamID,
					   new Term [] { stream, data, world });
		    }
		} catch (IOException ex) {
		    System.out.println(ex.getMessage());
		    System.out.println("...continuing");
		    reduct = SuccessModule.termFail;
		}
                if (Tracer.reduction)
		    Tracer.traceRewrite(computation, term, reduct);
		term.update(reduct);
		break;
	    }
	}

        public String printAsTxtLoadable() {
            return "external function \"appendToStream\"";
        }
    }

    private final static DataSymbol appendToStreamOperation
	= new OperationSymbol
	(moduleName,
	 appendToStreamName,
	 DataSymbol.toplevel,
	 3,
	 DataSymbol.NonInfix,
	 DataSymbol.NonInfix,
	 Symbol.Public,
	 // #Opaque -> [Char] -> IO ()
	 makeFunctionType
	 (makeSimpleType("IOauxModule.?"),
	  makeFunctionType(stringType, IOModule.mkIOType(unitType))),
	 new Instruction[]{
	    new appendToStreamInstruction(),
	});
	
    // ------------------------------------------------------------------
    
    private static class readFileInstruction implements Instruction {
        public void execute(Computation computation) {
            Term term = computation.getTerm();
            Term[] argument = term.getArgument();
            Term reduct;
            StringBuffer sb = new StringBuffer();

            String fileName = Util.convertTermToString(argument[0]);
            try {
                FileReader fr = new FileReader(fileName);
                BufferedReader br = new BufferedReader(fr);
                int lines = 0;
                int newChar = 0;

                while (newChar >= 0) {
                    newChar = br.read();
                    if (newChar >= 0) sb.append((char) newChar);
                }
		String fileData = new String(sb);

                reduct = Util.convertStringToTerm(fileData);
                reduct = new Term(tupleID, new Term[]{reduct,argument[1]});

            } catch (Exception e) {
                reduct = SuccessModule.termFail;
            }
            Space.instance.current = reduct;
        }

        public String printAsTxtLoadable() {
            return "external function \"readFile\"";
        }
    }

    private final static DataSymbol readFileOperation
	= new OperationSymbol
	(moduleName,
	 "readFile",
	 DataSymbol.toplevel,
	 2,
	 DataSymbol.NonInfix,
	 DataSymbol.NonInfix,
	 Symbol.Public,
	 // String -> IO String
	 makeFunctionType(stringType,IOModule.mkIOType(stringType)),
	 new Instruction[]{
	    new readFileInstruction(),
	    Replace.singleton,
	});

    // ------------------------------------------------------------------

    private static class writeFileInstruction implements Instruction {
        public void execute(Computation computation) {
            Term term = computation.getTerm();
	    Term file = term.getArgument((byte) 0);
            String fileName = Util.convertTermToString(file);
            Term reduct;
	    try {
		BufferedWriter br = new BufferedWriter
		    (new OutputStreamWriter
		     (new FileOutputStream(fileName)));
		Term stream = new Term(new TermImplOpaque(br));
		Term data = term.getArgument((byte) 1);
                Term world = term.getArgument((byte) 2);
		reduct = new Term(appendToStreamID, new Term [] { stream, data, world });
	    } catch (Exception e) {
		reduct = SuccessModule.termFail;
	    }
            Space.instance.current = reduct;
        }

        public String printAsTxtLoadable() {
            return "external function \"writeFile\"";
        }
    }

    private final static DataSymbol writeFileOperation
	= new OperationSymbol
	(moduleName,
	 "writeFile",
	 DataSymbol.toplevel,
	 3,
	 DataSymbol.NonInfix,
	 DataSymbol.NonInfix,
	 Symbol.Public,
	 // String -> String -> IO ()
	 makeFunctionType(stringType,makeFunctionType(stringType,IOModule.mkIOType(unitType))),
	 new Instruction[]{
	    new writeFileInstruction(),
	    Replace.singleton,
	});

    // ------------------------------------------------------------------

    private static class appendFileInstruction implements Instruction {
        public void execute(Computation computation) {
            Term term = computation.getTerm();
            Term[] argument = term.getArgument();
            Term reduct;
            String fileName = Util.convertTermToString(argument[0]);
	    try {
		BufferedWriter br
		    = new BufferedWriter(new FileWriter(fileName, true));
		Term stream = new Term(new TermImplOpaque(br));
		Term data = argument[1];
                Term world = argument[2];
		reduct = new Term(appendToStreamID, 
				  new Term [] { stream, data, world });
	    } catch (Exception e) {
		reduct = SuccessModule.termFail;
	    }
            Space.instance.current = reduct;
        }
	
        public String printAsTxtLoadable() {
            return "external function \"appendFile\"";
        }
    }

    private final static DataSymbol appendFileOperation
	= new OperationSymbol
	(moduleName,
	 "appendFile",
	 DataSymbol.toplevel,
	 3,
	 DataSymbol.NonInfix,
	 DataSymbol.NonInfix,
	 Symbol.Public,
	 // String -> String -> IO ()
	 makeFunctionType(stringType,makeFunctionType(stringType,IOModule.mkIOType(unitType))),
	 new Instruction[]{
	    new appendFileInstruction(),
	    Replace.singleton,
	});
    
}
