package com.redhat.ceylon.compiler.js;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.redhat.ceylon.compiler.loader.ModelEncoder;
import com.redhat.ceylon.compiler.typechecker.model.Module;

/** A container for things we need to keep per-module. */
public class JsOutput {
    private File outfile;
    private File modfile;
    private Writer writer;
    protected String clalias = "";
    protected final Module module;
    private final Set<File> s = new HashSet<File>();
    final Map<String,String> requires = new HashMap<String,String>();
    final MetamodelVisitor mmg;
    final String encoding;

    protected JsOutput(Module m, String encoding) throws IOException {
        this.encoding = encoding == null ? "UTF-8" : encoding;
        module = m;
        mmg = new MetamodelVisitor(m);
    }
    protected Writer getWriter() throws IOException {
        if (writer == null) {
            outfile = File.createTempFile("ceylon-jsout-", ".tmp");
            writer = new OutputStreamWriter(new FileOutputStream(outfile), encoding);
        }
        return writer;
    }
    protected File close() throws IOException {
        if (writer != null) {
            writer.close();
        }
        return outfile;
    }
    File getModelFile() {
        return modfile;
    }

    void addSource(File src) {
        s.add(src);
    }
    Set<File> getSources() { return s; }

    public void encodeModel(final JsIdentifierNames names) throws IOException {
        if (modfile == null) {
            modfile = File.createTempFile("ceylon-jsmod-", ".tmp");
            try (OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream(modfile), encoding)) {
                JsCompiler.beginWrapper(fw);
                fw.write("ex$.$CCMM$=");
                ModelEncoder.encodeModel(mmg.getModel(), fw);
                fw.write(";\n");
                JsCompiler.endWrapper(fw);
            } finally {
            }
            out("\nvar _CTM$;function $CCMM$(){if (_CTM$===undefined)_CTM$=require('",
                    JsCompiler.scriptPath(module), "-model').$CCMM$;return _CTM$;}\n");
            getWriter().write("ex$.$CCMM$=$CCMM$;\n");
            if (!JsCompiler.isCompilingLanguageModule()) {
                Module clm = module.getLanguageModule();
                clalias = names.moduleAlias(clm) + ".";
                require(clm, names);
            }
        }
    }

    public void outputFile(File f) {
        try(BufferedReader r = new BufferedReader(new FileReader(f))) {
            String line = null;
            while ((line = r.readLine()) != null) {
                final String c = line.trim();
                if (!c.isEmpty()) {
                    getWriter().write(c);
                    getWriter().write('\n');
                }
            }
        } catch(IOException ex) {
            throw new CompilerErrorException("Reading from " + f);
        }
    }

    public String getLanguageModuleAlias() {
        return clalias;
    }

    public void require(final Module mod, final JsIdentifierNames names) {
        final String path = JsCompiler.scriptPath(mod);
        final String modAlias = names.moduleAlias(mod);
        if (requires.put(path, modAlias) == null) {
            out("var ", modAlias, "=require('", path, "');\n");
            if (modAlias != null && !modAlias.isEmpty()) {
                out(clalias, "$addmod$(", modAlias,",'", mod.getNameAsString(), "/", mod.getVersion(), "');\n");
            }
        }
    }

    /** Print generated code to the Writer.
     * @param code The main code
     * @param codez Optional additional strings to print after the main code. */
    public void out(String code, String... codez) {
        try {
            getWriter().write(code);
            for (String s : codez) {
                getWriter().write(s);
            }
        }
        catch (IOException ioe) {
            throw new RuntimeException("Generating JS code", ioe);
        }
    }

}
@@ -671,24 +671,57 @@
 ule module;%0A
+    protected boolean modelDone;%0A
     private 
@@ -831,24 +831,34 @@
 ing%3E();%0A    
+protected 
 final Metamo
@@ -1687,119 +1687,115 @@
     
-public void encodeModel(final JsIdentifierNames names) throws IOException %7B%0A        if (modfile == null) %7B%0A    
+/** Writes the model file to a temporary file. */%0A    protected void writeModelFile() throws IOException %7B%0A
     
@@ -1846,36 +1846,32 @@
 mod-%22, %22.tmp%22);%0A
-    
         try (Out
@@ -1962,36 +1962,32 @@
 ) %7B%0A            
-  
-  
 JsCompiler.begin
@@ -2003,36 +2003,32 @@
 w);%0A            
-    
 fw.write(%22ex$.$C
@@ -2032,28 +2032,24 @@
 .$CCMM$=%22);%0A
-    
             
@@ -2098,36 +2098,32 @@
 w);%0A            
-    
 fw.write(%22;%5Cn%22);
@@ -2119,28 +2119,24 @@
 ite(%22;%5Cn%22);%0A
-    
             
@@ -2174,37 +2174,419 @@
     
-    %7D finally %7B%0A            %7D
+%7D finally %7B%0A        %7D%0A    %7D%0A%0A    /** Write the function to retrieve or define the model JSON map. */%0A    protected void writeModelRetriever() throws IOException %7B%0A        out(%22require('%22, JsCompiler.scriptPath(module), %22-model').$CCMM$%22);%0A    %7D%0A%0A    public void encodeModel(final JsIdentifierNames names) throws IOException %7B%0A        if (!modelDone) %7B%0A            modelDone = true;%0A            writeModelFile();
 %0A   
@@ -2657,27 +2657,19 @@
 d)_CTM$=
-require('%22,
+%22);
 %0A       
@@ -2677,63 +2677,48 @@
     
-        JsCompiler.scriptPath(module), %22-model').$CCMM$
+writeModelRetriever();%0A            out(%22
 ;ret

fix #428