

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.TreeSet;

import junit.framework.TestCase;
import metalexer.CompilationError;
import metalexer.FileLoader;
import metalexer.ast.Layout;
import metalexer.jflex.JFlexHelper;
import metalexer.jflex.PackageFind;

public class PartialCompilation extends TestCase {
    public static final JFlexHelper JFLEX_HELPER = new JFlexHelper();

    private static final String BEAVER_PATH = "lib/beaver-0.9.6.1/lib/beaver.jar";
    private static final String PLACEHOLDER_PATH = "test/backend-jflex/placeholders";

    private static final String IN_BASE_DIR = "test/backend-jflex/in";
    private static final String OUT_BASE_DIR = "test/backend-jflex/out";
    private static final String OUT_BIN_DIR = OUT_BASE_DIR + "/bin";

    static {
        new File(IN_BASE_DIR).mkdirs();
        new File(OUT_BASE_DIR).mkdirs();
        new File(OUT_BIN_DIR).mkdirs();
    }

    public void test_metalexer_component() {
        compile("metalexer", "component");
    }

    public void test_metalexer_layout() {
        compile("metalexer", "layout");
    }

    public void test_natlab_natlab() {
        compile("natlab", "natlab");
    }

    public void test_natlab_annotations() {
        compile("natlab", "annotations");
    }

    public void test_natlab_annotated_natlab() {
        compile("natlab", "annotated_natlab");
    }

    public void test_natlab_attributed_natlab() {
        compile("natlab", "attributed_natlab");
    }

    public void test_natlabnaive_natlab() {
        compile("natlab-naive", "natlab");
    }

    private static void compile(String groupName, String layoutName) {
        String inDir = IN_BASE_DIR + "/" + groupName;
        String outDir = OUT_BASE_DIR + "/" + groupName + "/" + layoutName;

        // compile layout
        compileLayout(layoutName, inDir, outDir);

        // compile jflex
        ClassInfo lexerClassInfo = compileJFlex(outDir, outDir + "/" + layoutName + metalexer.jflex.Constants.JFLEX_FILE_EXT);
        ClassInfo metaLexerClassInfo = compileJFlex(outDir, outDir + "/" + layoutName + metalexer.jflex.Constants.META_JFLEX_FILE_EXT);

        // compile java
        compileJava(outDir, "", metaLexerClassInfo);
        compileJava(outDir, outDir + ":" + BEAVER_PATH + ":" + PLACEHOLDER_PATH, lexerClassInfo);
    }
    
    public static void main(String[] args) {
        String groupName = "natlab";
        String layoutName = "natlab";
        String outDir = OUT_BASE_DIR + "/" + groupName + "/" + layoutName;
        ClassInfo metaLexerClassInfo = compileJFlex(outDir, outDir + "/" + layoutName + metalexer.jflex.Constants.META_JFLEX_FILE_EXT);
        compileJava(outDir, "", metaLexerClassInfo);
        
        ClassInfo lexerClassInfo = compileJFlex(outDir, outDir + "/" + layoutName + metalexer.jflex.Constants.JFLEX_FILE_EXT);
        compileJava(outDir, outDir + ":" + BEAVER_PATH + ":" + PLACEHOLDER_PATH, lexerClassInfo);
    }

    //basically the same as ML2JFlex except it has less output and includes calls to fail()
    private static void compileLayout(String layoutName, String inDir, String outDir) {
        SortedSet<CompilationError> errors = new TreeSet<CompilationError>();
        Layout layout = null;
        try {
            FileLoader loader = new FileLoader(inDir);
            layout = loader.loadLayout(layoutName, errors);
            if(layout != null) {
                layout = layout.processInheritance(loader, errors);
                if(layout.getHelper()) {
                    errors.add(layout.makeCompilationError(layout.getName() + " is a helper layout."));
                }
            }
        } catch(IOException e) {
            errors.add(new CompilationError(null, e.getMessage()));
        }
        if(errors.isEmpty()) {
            try {
                layout.tidyRuleGroups();
                layout.assignCharsToMetaTokens(new metalexer.jflex.CharacterAssigner(new metalexer.jflex.CharacterGenerator()));
                layout.generateJFlex(outDir);
                layout.generateMetaJFlex(outDir);
                layout.generateJFlexMacroPropFile(outDir);
                layout.generateJFlexEmbeddingPropFile(outDir);
            } catch (IOException e) {
                System.out.println(new CompilationError(null, e.getMessage()));
                fail();
            }
        } else {
            for(CompilationError error : errors) {
                System.out.println(error);
            }
            fail();
        }
        System.err.println("Compiled metalexer: " + layoutName);
    }

    private static ClassInfo compileJFlex(String destDir, String file) {
        ClassInfo classInfo = null;
        try {
            String packageName = getPackageName(file);
            String className = getClassName(file);
            classInfo = new ClassInfo(packageName, className);
        } catch(IOException e) {
            throw new RuntimeException("Failed to load class info from " + file, e);
        }

        JFLEX_HELPER.generate(file, destDir + "/" + classInfo.getDirName());

        System.err.println("Compiled jflex: " + file);
        return classInfo;
    }

    private static String getPackageName(String jflexFileName) throws IOException {
        BufferedReader fileIn = new BufferedReader(new FileReader(jflexFileName));
        StringBuffer header = new StringBuffer();
        while(fileIn.ready()) {
            String line = fileIn.readLine();
            if(line.trim().startsWith("%%")) {
                break;
            }
            header.append(line + "\n");
        }
        String packageName = PackageFind.findPackage(header.toString());
        if(packageName == null) {
            throw new RuntimeException("Can't find package name for " + jflexFileName);
        }
        return packageName;
    }

    private static String getClassName(String jflexFileName) throws IOException {
        BufferedReader fileIn = new BufferedReader(new FileReader(jflexFileName));
        while(fileIn.ready()) {
            StringTokenizer tokenizer = new StringTokenizer(fileIn.readLine());
            if(tokenizer.hasMoreTokens()) {
                if(tokenizer.nextToken().equals("%class")) {
                    if(tokenizer.hasMoreTokens()) {
                        return tokenizer.nextToken();
                    }
                }
            }
        }
        throw new RuntimeException("Can't find class name for " + jflexFileName);
    }

    private static void compileJava(String outDir, String classpath, ClassInfo classInfo) {
        String dirName = outDir + "/" + classInfo.getDirName() + "/";
        String javaFileName = classInfo.getClassName() + ".java";
        assertEquals("javac status", 0, com.sun.tools.javac.Main.compile(new String[] {"-d", OUT_BIN_DIR, "-classpath", classpath, dirName + javaFileName}));
        System.err.println("Compiled java: " + classInfo);
    }

    private static class ClassInfo {
        private final String packageName;
        private final String className;
        public ClassInfo(String packageName, String className) {
            if(packageName != null && packageName.length() == 0) {
                this.packageName = null;
            } else {
                this.packageName = packageName;
            }
            this.className = className;
        }
        public String getPackageName() {
            return packageName;
        }
        public String getDirName() {
            if(packageName == null) {
                return "";
            }
            return packageName.replaceAll("\\.", "/");
        }
        public String getClassName() {
            return className;
        }
        public String toString() {
            if(packageName == null) {
                return className;
            } else {
                return packageName + "." + className;
            }
        }
    }
}
