package com.fitbank.modularjs;

import java.io.*;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.yahoo.platform.yui.compressor.YUICompressor;
import org.apache.maven.plugin.logging.Log;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;

public class ModularJsBuilder {

    private static final Pattern INCLUDE_REGEX =
            Pattern.compile("^(\\s*)include\\(['\"](.*)['\"]\\);\r?$");

    public static Log log;

    public static final Set<String> EXCLUDE_CONCAT = new HashSet<String>();

    private List<String> INCLUDED = new LinkedList<String>();

    private List<String> INCLUDING = new LinkedList<String>();

    public static File module2file(File baseDir, String name, boolean mobile) {
        return new File(baseDir, name.replace(".", File.separator) + (mobile ? "-mobile" : "") + ".js");
    }

    public static String file2module(File baseDir, File file) {
        return baseDir.toURI().relativize(file.toURI()).getPath().replaceAll(".js$", "").replace(File.separator, ".");
    }

    private ModularJsBuilder() {
    }

    public static void build(File rootDir, File outputDir) throws IOException {
        buildDir(rootDir, outputDir, rootDir);
    }

    private static void buildDir(File rootDir, File outputDir, File currentDir) throws IOException {
        for (File file : currentDir.listFiles(new FileFilter() {

            @Override
            public boolean accept(File pathname) {
                return pathname.isDirectory() ||
                        (pathname.getName().endsWith(".js")
                                && !pathname.getName().endsWith(".compressed.js")
                                && !pathname.getName().endsWith(".build.js"));
            }

        })) {
            if (file.isDirectory()) {
                buildDir(rootDir, outputDir, file);
            } else {
                new ModularJsBuilder().buildFile(rootDir, outputDir, file, false);
                new ModularJsBuilder().buildFile(rootDir, outputDir, file, true);
            }
        }
    }

    private void buildFile(File rootDir, File outputDir, File file, boolean mobile) throws IOException {
        StringWriter out = new StringWriter();
        processFile(rootDir, new BufferedReader(new FileReader(file)), out, false);
        String compiledJs = out.toString();

        File build = new File(outputDir, file2module(rootDir, file) + (mobile ? ".build-mobile.js" : ".build.js"));
        FileWriter buildFw = new FileWriter(build);
        IOUtil.copy(compiledJs, buildFw);
        buildFw.close();

        File compressed = new File(outputDir, file2module(rootDir, file) +
                (mobile ? ".build.compressed-mobile.js" : ".build.compressed.js"));
        try {
            YUICompressor.main(new String[]{build.getAbsolutePath(), "-o", compressed.getAbsolutePath()});
        } catch (Exception e) {
            log.error("    [E] Cannot compress " + build.getAbsolutePath()
                    + ": " + e.getLocalizedMessage());
            FileUtils.copyFile(build, compressed);
        }
    }

    private void processFile(File rootDir, BufferedReader in, Writer out, boolean mobile) throws IOException {
        processFile(rootDir, in, out, "", mobile);
    }

    private void processFile(File rootDir, BufferedReader in, Writer out, String indent, boolean mobile) throws IOException {
        String line;
        while ((line = in.readLine()) != null) {
            Matcher m = INCLUDE_REGEX.matcher(line);

            if (m.find()) {
                String module = m.group(2);
                if (!include(rootDir, out, module, m.group(1), mobile)) {
                    out.write(indent + line);
                }
            } else {
                out.write(indent + line);
            }

            out.write(System.getProperty("line.separator"));
        }
    }

    private boolean include(File rootDir, Writer out, String module, String indent, boolean mobile) throws IOException {
        if (INCLUDED.contains(module)) {
            return true;
        }

        if (EXCLUDE_CONCAT.contains(module)) {
            INCLUDED.add(module);
            return false;
        }

        if (INCLUDING.contains(module)) {
            throw new IOException("Recursive import of module: " + module);
        }

        File moduleFile = ModularJsBuilder.module2file(rootDir, module, mobile);

        if (mobile && !moduleFile.exists()) {
            moduleFile = ModularJsBuilder.module2file(rootDir, module, false);
        }

        if (!moduleFile.exists()) {
            log.debug("    [E] Module " + module + " not found");
            return false;
        }

        INCLUDING.add(module);

        out.write("\nif (!modularjs.loaded[\"" + module + "\"]) {\n");
        processFile(rootDir, new BufferedReader(new FileReader(moduleFile)), out, indent, mobile);
        out.write("\n}\n");

        INCLUDING.remove(module);

        out.write("\nmodularjs.loaded[\"" + module + "\"] = true;\n");

        INCLUDED.add(module);

        log.debug("    [L] Module " + module + " loaded");

        return true;
    }

}
