package se.lth.control.labcomm2006.compiler;

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

public class LabComm {

  private static void println(String s) {
    System.out.println(s);
  }

  private static void print_help() {
    println("\n Usage: java -jar labcom.jar [options*] FILE");
    println("");
    println(" --help                  Shows this help text");
    println(" -v                      Be verbose");
    println(" --ver=VERSION           Generate code for labcomm VERSION (=2006 or 2013)");
    println("[ C options ]");
    println(" -C                      Generates C/H code in FILE.[ch]");
    println(" --cprefix=PREFIX        Prefixes C types with PREFIX");
    println(" --cinclude=FILE         Include FILE in generated .c");
    println(" --c=CFILE               Generates C code in CFILE");
    println(" --h=HFILE               Generates H code in HFILE");
    println("[ C# options]");
    println(" --cs                    Generates C# code in FILE.cs");
    println(" --cs=CSFILE             Generates C# code in CSFILE");
    println(" --csnamespace=NAMESPACE Place C# classes in NAMESPACE");
    println("[ Java options ]");
    println(" --java=JDIR             Generates Java files in JDIR");
    println(" --javapackage=PACKAGE   Place Java classes in PACKAGE");
    println("[ Python options ]");
    println(" -P                      Generates Python code in FILE.py");
    println(" --python=PFILE          Generates Python code in PFILE");
    println("[ RAPID options ]");
    println(" --rapid                 Generates RAPID code in FILE.sys");
    println("[ Misc options ]");
    println(" --pretty=PFILE          Pretty prints to PFILE");
    println(" --typeinfo=TIFILE       Generates typeinfo in TIFILE");
  }
    
  /** To be cleaned up.
   */
  private static void checkVersion(int v) {
     if(! (v == 2006 || v == 2013) ) {
	System.err.println(" Unknown version: " + v);
	System.err.println(" Supported versions: 2006, 2013 ");
	System.exit(2);
     }
  }

  private static void genH(Program p, String hName, 
			   Vector cIncludes, String coreName, String prefix, int ver) {
    try {
      FileOutputStream f;
      PrintStream out;
      
      f = new FileOutputStream(hName);
      out = new PrintStream(f);
      p.C_genH(out, cIncludes, coreName, prefix, ver);
      out.close();
    } catch (IOException e) {
      System.err.println("IOException: " + hName + " " + e);
    }
  }

  private static void genC(Program p, String cName, 
			   Vector cIncludes, String coreName, String prefix, int ver) {
    try {
      FileOutputStream f;
      PrintStream out;

      f = new FileOutputStream(cName);
      out = new PrintStream(f);
      p.C_genC(out, cIncludes, coreName, prefix, ver);
      out.close();
    } catch (IOException e) {
      System.err.println("IOException: " + cName + " " + e);
    }
  }

  private static void genCS(Program p, String csName, String csNamespace, int ver) {
//      throw new Error("C# generation currently disabled");
    try {
      p.CS_gen(csName, csNamespace, ver);
    } catch (IOException e) {
      System.err.println("IOException: " + csName + " " + 
			 csNamespace + " " + e);
    }
  }

  private static void genJava(Program p,  String dirName, String packageName, int ver) {
    try {
      p.J_gen(dirName, packageName, ver);
    } catch (IOException e) {
      System.err.println("IOException: " + dirName + " " + 
			 packageName + " " + e);
    }
  }

  private static void genPython(Program p, String filename, String prefix, int ver) {
    try {
      FileOutputStream f;
      PrintStream out;

      f = new FileOutputStream(filename);
      out = new PrintStream(f);
      p.Python_gen(out, prefix, ver);
      out.close();
    } catch (IOException e) {
      System.err.println("IOException: " + filename + " " + e);
    }
  }

  private static void genRAPID(Program p, String filename, String prefix, int ver) {
    try {
      p.RAPID_gen(filename, prefix, ver);
    } catch (IOException e) {
      System.err.println("IOException: " + filename + " " + e);
    }
  }

  /** Helper class to contain command line options 
      and their associated behaviour
   **/
  private static class Opts {
    final String[] args;
    String coreName = null;
    String prefix = null;
    boolean verbose = false;
    int ver = 2006; // Version 2006 fixed
    String cFile = null;
    String hFile = null;
    Vector cIncludes = new Vector();
    String cPrefix; // gets default value (prefix) in processFilename
    String csFile = null;
    String csNamespace = null;
    String javaDir = null;
    String javaPackage = "";
    String pythonFile = null;
    String prettyFile = null;
    String typeinfoFile = null;
    String rapidFile = null;
    String fileName = null;

   Opts(String[] args) {
     this.args = args;
   }

    private static String getCoreName(String s) {
      int i = s.lastIndexOf('.');
      return s.substring(0, i > 0 ? i : s.length());
    }
  
    private static String getFileName(String s) {
      return s.substring(s.lastIndexOf('/') + 1, s.length());
    }
  
    private static String getBaseName(String s) {
      s = getFileName(s);
      int i = s.lastIndexOf('.');
      return s.substring(0, i > 0 ? i : s.length());
    }
  
    private static String getPrefix(String s) {
      return s.substring(s.lastIndexOf('/') + 1, s.length());
    }
  
    boolean processFilename(){
      // Scan for first non-option
      for (int i = 0 ; i < args.length ; i++) {
        if (! args[i].startsWith("-")) {
  	fileName = args[i];
  	break;
        }
      }
      if (fileName != null) {
        coreName = getBaseName(fileName);
        prefix = getPrefix(coreName);
       cPrefix = prefix;
      }
      return fileName != null;
    }
    
    void processArgs(){
      for (int i = 0 ; i < args.length ; i++) {
        if (fileName == null ||
  	  args[i].equals("-help") || 
  	  args[i].equals("-h") || 
  	  args[i].equals("--help")) {
  	print_help();
  	System.exit(0);
        } else if (args[i].equals("-v")) {
  	verbose=true;
        } else if (args[i].startsWith("--ver=")) {
          /* ver = Integer.parseInt(args[i].substring(6));
             checkVersion(ver); */
        } else if (args[i].equals("-C")) {
  	cFile = coreName + ".c";
  	hFile = coreName + ".h";
        } else if (args[i].startsWith("--cinclude=")) {
  	cIncludes.add(args[i].substring(11));
        } else if (args[i].startsWith("--cprefix=")) {
  	cPrefix = args[i].substring(10);
        } else if (args[i].startsWith("--c=")) {
  	cFile = args[i].substring(4);
        } else if (args[i].startsWith("--h=")) {
  	hFile = args[i].substring(4);
        } else if (args[i].equals("--cs")) {
  	csFile = coreName + ".cs";
        } else if (args[i].startsWith("--cs=")) {
  	csFile = args[i].substring(5);
        } else if (args[i].startsWith("--csnamespace=")) {
  	csNamespace = args[i].substring(14);
        } else if (args[i].startsWith("--java=")) {
  	javaDir = args[i].substring(7);
        } else if (args[i].startsWith("--javapackage=")) {
  	javaPackage = args[i].substring(14);
        } else if (args[i].equals("-P")) {
  	pythonFile = coreName + ".py";
        } else if (args[i].startsWith("--python=")) {
  	pythonFile = args[i].substring(9);
        } else if (args[i].startsWith("--pretty=")) {
  	prettyFile = args[i].substring(9);
        } else if (args[i].startsWith("--typeinfo=")) {
  	typeinfoFile = args[i].substring(11);
        } else if (args[i].equals("--rapid")) {
  	rapidFile = coreName + ".sys";
        } else if (i == args.length - 1) {
  	fileName = args[i];
        } else {
  	System.err.println(" Unknown argument " + args[i]);
  	print_help();
  	System.exit(2);
        }
      }
     if(prefix==null){
  	System.err.println("   WARNING! prefix==null");
        prefix="";
     }
   }

   Program parseFile(){
     Program ast = null;
     try {
       // Check for errors
       LabCommScanner scanner = new LabCommScanner(
                                  new FileReader(fileName));
       LabCommParser parser = new LabCommParser();
       Program p = (Program)parser.parse(scanner);
       Collection errors = new LinkedList();
       p.errorCheck(errors);
         
       if (errors.isEmpty()) {
         ast = p;
       } else {
         for (Iterator iter = errors.iterator(); iter.hasNext(); ) {
           String s = (String)iter.next();
           System.out.println(s);
         }
       }
     } catch (FileNotFoundException e) {
       System.err.println("Could not find file: " + fileName);
     } catch (IOException e) {
       System.err.println("IOException: " + fileName + " " + e);
     } catch (beaver.Parser.Exception e) {
       System.err.println(e.getMessage());
     }
     return ast;
   }

   boolean generateC(Program ast) {
     boolean wroteFile = false; 
     Vector hIncludes = new Vector(cIncludes);
     if (hFile != null) {
       cIncludes.add(hFile);
     }
     if (cFile != null) {
       printStatus("C: " , cFile);
       genC(ast, cFile, cIncludes, coreName, cPrefix, ver);
       wroteFile = true;
     }
     if (hFile != null) {
       printStatus("H: " , hFile);
       genH(ast, hFile, hIncludes, coreName, cPrefix, ver);
       wroteFile = true;
     }
     return wroteFile;
   }
  
   boolean generateCS(Program ast) {
     boolean wroteFile = false; 
     if (csFile != null) {
       printStatus("C#: " , csFile); 
       genCS(ast, csFile, csNamespace, ver);
       wroteFile = true;
     }
     return wroteFile;
   }
  
   boolean generateJava(Program ast) {
     boolean wroteFile = false; 
     if (javaDir != null) {
       printStatus("Java: " , javaDir);
       genJava(ast, javaDir, javaPackage, ver);
       wroteFile = true;
     }
     return wroteFile;
   }
  
   boolean generatePython(Program ast) {
     boolean wroteFile = false; 
     if (pythonFile != null) {
       printStatus("Python: " , pythonFile); 
       genPython(ast, pythonFile, prefix, ver);
       wroteFile = true;
     }
     return wroteFile;
   }
  
   boolean generateRAPID(Program ast) {
     boolean wroteFile = false; 
     if (rapidFile != null) {
       printStatus("RAPID: " , rapidFile);
       genRAPID(ast, rapidFile, coreName, ver);
       wroteFile = true;
     }
     return wroteFile;
   }
   boolean generatePrettyPrint(Program ast) {
     boolean wroteFile = false; 
     if (prettyFile != null) {
       printStatus("Pretty: " , prettyFile); 
       try {
         FileOutputStream f = new FileOutputStream(prettyFile);
         PrintStream out = new PrintStream(f);
         ast.pp(out);
         out.close();
         wroteFile = true;
       } catch (IOException e) {
         System.err.println("IOException: " + prettyFile + " " + e);
       } 
     }
     return wroteFile;
   }
  
   boolean generateTypeinfo(Program ast) {
     boolean wroteFile = false; 
     if (typeinfoFile != null) {
       printStatus("TypeInfo: " , typeinfoFile); 
       try {
         FileOutputStream f = new FileOutputStream(typeinfoFile);
         PrintStream out = new PrintStream(f);
         ast.C_info(out, cPrefix, ver);
         ast.Java_info(out, ver);
         ast.CS_info(out, csNamespace, ver);
         wroteFile = true;
       } catch (IOException e) {
         System.err.println("IOException: " + typeinfoFile + " " + e);
       }
     }
     return wroteFile;
    }

    private void printStatus(String kind, String filename){
       if (verbose) { 
         System.err.println("Generating "+kind+": " + filename); 
       }
    }
  }


  public static void main(String[] args) {
    Opts opts = new Opts(args);
    if(!opts.processFilename()) {
      print_help();
      System.exit(1);
    } else {
      opts.processArgs();
      Program ast =  opts.parseFile();

      if (ast != null) {
	
	boolean fileWritten = false;

        fileWritten |= opts.generateC(ast);
        fileWritten |= opts.generateCS(ast);
        fileWritten |= opts.generateJava(ast);
        fileWritten |= opts.generatePython(ast);
        fileWritten |= opts.generateRAPID(ast);
        fileWritten |= opts.generatePrettyPrint(ast);
        fileWritten |= opts.generateTypeinfo(ast);

        // if no output to files, prettyprint on stdout
	if (!fileWritten) {
	  ast.pp(System.out);
	}
      } else {
          // Catch-all for compilation errors
          System.err.println("Error in specification");
          System.exit(3);
      }
    }
  } 
}