/**
 * Copyright [2013] Gaurav Gupta
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package io.github.chris2011.netbeans.minifierbeans.util.source.minify;

import com.google.javascript.jscomp.CommandLineRunner;
import com.google.javascript.jscomp.CompilationLevel;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.Compiler;
import com.google.javascript.jscomp.SourceFile;
import com.googlecode.htmlcompressor.compressor.HtmlCompressor;
import com.googlecode.htmlcompressor.compressor.XmlCompressor;
import com.yahoo.platform.yui.compressor.CssCompressor;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import io.github.chris2011.netbeans.minifierbeans.ui.MinifyProperty;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;

public class MinifyUtil {

    MinifyResult minify(FileObject parentFile, MinifyProperty minifyProperty) {
        int directory = 0, cssFile = 0, jsFile = 0, htmlFile = 0, xmlFile = 0, jsonFile = 0;
        MinifyResult minifyResult = new MinifyResult();

        for (FileObject file : parentFile.getChildren()) {
            if (file.isFolder()) {
                directory++;
                MinifyResult preMinifyResult = minify(file, minifyProperty);
                directory = directory + preMinifyResult.getDirectories();
                cssFile = cssFile + preMinifyResult.getCssFiles();
                jsFile = jsFile + preMinifyResult.getJsFiles();
                htmlFile = htmlFile + preMinifyResult.getHtmlFiles();
                xmlFile = xmlFile + preMinifyResult.getXmlFiles();
                jsonFile = jsonFile + preMinifyResult.getJsonFiles();

                minifyResult.setInputJsFilesSize(minifyResult.getInputJsFilesSize() + preMinifyResult.getInputJsFilesSize());
                minifyResult.setOutputJsFilesSize(minifyResult.getOutputJsFilesSize() + preMinifyResult.getOutputJsFilesSize());
                minifyResult.setInputCssFilesSize(minifyResult.getInputCssFilesSize() + preMinifyResult.getInputCssFilesSize());
                minifyResult.setOutputCssFilesSize(minifyResult.getOutputCssFilesSize() + preMinifyResult.getOutputCssFilesSize());
                minifyResult.setInputHtmlFilesSize(minifyResult.getInputHtmlFilesSize() + preMinifyResult.getInputHtmlFilesSize());
                minifyResult.setOutputHtmlFilesSize(minifyResult.getOutputHtmlFilesSize() + preMinifyResult.getOutputHtmlFilesSize());
                minifyResult.setInputXmlFilesSize(minifyResult.getInputXmlFilesSize() + preMinifyResult.getInputXmlFilesSize());
                minifyResult.setOutputXmlFilesSize(minifyResult.getOutputXmlFilesSize() + preMinifyResult.getOutputXmlFilesSize());
                minifyResult.setInputJsonFilesSize(minifyResult.getInputJsonFilesSize() + preMinifyResult.getInputJsonFilesSize());
                minifyResult.setOutputJsonFilesSize(minifyResult.getOutputJsonFilesSize() + preMinifyResult.getOutputJsonFilesSize());
            } else if (file.getExt().equalsIgnoreCase("js") && minifyProperty.isBuildJSMinify()) {
                jsFile++;
                try {
                    Boolean allow = true;
                    String inputFilePath = file.getPath();
                    String outputFilePath;

                    if (minifyProperty.isSkipPreExtensionJS() && minifyProperty.isBuildJSMinify() && minifyProperty.isNewJSFile()) {
                        if (minifyProperty.getPreExtensionJS() != null && !minifyProperty.getPreExtensionJS().trim().isEmpty()
                                && file.getName().matches(".*" + Pattern.quote(minifyProperty.getPreExtensionJS()))) {
                            allow = false;
                        }
                    }
                    if (allow) {
                        if (minifyProperty.isNewJSFile() && minifyProperty.getPreExtensionJS() != null && !minifyProperty.getPreExtensionJS().trim().isEmpty()) {
                            outputFilePath = file.getParent().getPath() + File.separator + file.getName() + minifyProperty.getPreExtensionJS() + "." + file.getExt();
                        } else {
                            outputFilePath = inputFilePath;
                        }

                        MinifyFileResult minifyFileResult = compress(inputFilePath, "text/javascript", outputFilePath, minifyProperty);
                        minifyResult.setInputJsFilesSize(minifyResult.getInputJsFilesSize() + minifyFileResult.getInputFileSize());
                        minifyResult.setOutputJsFilesSize(minifyResult.getOutputJsFilesSize() + minifyFileResult.getOutputFileSize());
                    }
                } catch (IOException ex) {
                    Exceptions.printStackTrace(ex);
                }
            } else if (file.getExt().equalsIgnoreCase("css") && minifyProperty.isBuildCSSMinify()) {
                cssFile++;
                try {
                    Boolean allow = true;
                    String inputFilePath = file.getPath();
                    String outputFilePath;

                    if (minifyProperty.isSkipPreExtensionCSS() && minifyProperty.isBuildCSSMinify() && minifyProperty.isNewCSSFile()) {
                        if (minifyProperty.getPreExtensionCSS() != null && !minifyProperty.getPreExtensionCSS().trim().isEmpty()
                                && file.getName().matches(".*" + Pattern.quote(minifyProperty.getPreExtensionCSS()))) {
                            allow = false;
                        }
                    }
                    if (allow) {
                        if (minifyProperty.isNewCSSFile() && minifyProperty.getPreExtensionCSS() != null && !minifyProperty.getPreExtensionCSS().trim().isEmpty()) {
                            outputFilePath = file.getParent().getPath() + File.separator + file.getName() + minifyProperty.getPreExtensionCSS() + "." + file.getExt();
                        } else {
                            outputFilePath = inputFilePath;
                        }

                        MinifyFileResult minifyFileResult = compress(inputFilePath, "text/css", outputFilePath, minifyProperty);
                        minifyResult.setInputCssFilesSize(minifyResult.getInputCssFilesSize() + minifyFileResult.getInputFileSize());
                        minifyResult.setOutputCssFilesSize(minifyResult.getOutputCssFilesSize() + minifyFileResult.getOutputFileSize());
                    }
                } catch (IOException ex) {
                    Exceptions.printStackTrace(ex);
                }
            } else if (file.getExt().equalsIgnoreCase("html") && minifyProperty.isBuildHTMLMinify()) {
                htmlFile++;
                try {
                    Boolean allow = true;
                    String inputFilePath = file.getPath();
                    String outputFilePath;

                    if (minifyProperty.isSkipPreExtensionHTML() && minifyProperty.isBuildHTMLMinify() && minifyProperty.isNewHTMLFile()) {
                        if (minifyProperty.getPreExtensionHTML() != null && !minifyProperty.getPreExtensionHTML().trim().isEmpty()
                                && file.getName().matches(".*" + Pattern.quote(minifyProperty.getPreExtensionHTML()))) {
                            allow = false;
                        }
                    }
                    if (allow) {
                        if (minifyProperty.isNewHTMLFile() && minifyProperty.getPreExtensionHTML() != null && !minifyProperty.getPreExtensionHTML().trim().isEmpty()) {
                            outputFilePath = file.getParent().getPath() + File.separator + file.getName() + minifyProperty.getPreExtensionHTML() + "." + file.getExt();
                        } else {
                            outputFilePath = inputFilePath;
                        }

                        MinifyFileResult minifyFileResult = compress(inputFilePath, "text/html", outputFilePath, minifyProperty);
                        minifyResult.setInputHtmlFilesSize(minifyResult.getInputHtmlFilesSize() + minifyFileResult.getInputFileSize());
                        minifyResult.setOutputHtmlFilesSize(minifyResult.getOutputHtmlFilesSize() + minifyFileResult.getOutputFileSize());
                    }
                } catch (IOException ex) {
                    Exceptions.printStackTrace(ex);
                }
            } else if (file.getExt().equalsIgnoreCase("xml") && minifyProperty.isBuildXMLMinify()) {
                xmlFile++;
                try {
                    Boolean allow = true;
                    String inputFilePath = file.getPath();
                    String outputFilePath;

                    if (minifyProperty.isSkipPreExtensionXML() && minifyProperty.isBuildXMLMinify() && minifyProperty.isNewXMLFile()) {
                        if (minifyProperty.getPreExtensionXML() != null && !minifyProperty.getPreExtensionXML().trim().isEmpty()
                                && file.getName().matches(".*" + Pattern.quote(minifyProperty.getPreExtensionXML()))) {
                            allow = false;
                        }
                    }
                    if (allow) {
                        if (minifyProperty.isNewXMLFile() && minifyProperty.getPreExtensionXML() != null && !minifyProperty.getPreExtensionXML().trim().isEmpty()) {
                            outputFilePath = file.getParent().getPath() + File.separator + file.getName() + minifyProperty.getPreExtensionXML() + "." + file.getExt();
                        } else {
                            outputFilePath = inputFilePath;
                        }

                        MinifyFileResult minifyFileResult = compress(inputFilePath, "text/xml-mime", outputFilePath, minifyProperty);
                        minifyResult.setInputXmlFilesSize(minifyResult.getInputXmlFilesSize() + minifyFileResult.getInputFileSize());
                        minifyResult.setOutputXmlFilesSize(minifyResult.getOutputXmlFilesSize() + minifyFileResult.getOutputFileSize());
                    }
                } catch (IOException ex) {
                    Exceptions.printStackTrace(ex);
                }
            } else if (file.getExt().equalsIgnoreCase("json") && minifyProperty.isBuildJSONMinify()) {
                jsonFile++;
                try {
                    Boolean allow = true;
                    String inputFilePath = file.getPath();
                    String outputFilePath;

                    if (minifyProperty.isSkipPreExtensionJSON() && minifyProperty.isBuildJSONMinify() && minifyProperty.isNewJSONFile()) {
                        if (minifyProperty.getPreExtensionJSON() != null && !minifyProperty.getPreExtensionJSON().trim().isEmpty()
                                && file.getName().matches(".*" + Pattern.quote(minifyProperty.getPreExtensionJSON()))) {
                            allow = false;
                        }
                    }
                    if (allow) {
                        if (minifyProperty.isNewJSONFile() && minifyProperty.getPreExtensionJSON() != null && !minifyProperty.getPreExtensionJSON().trim().isEmpty()) {
                            outputFilePath = file.getParent().getPath() + File.separator + file.getName() + minifyProperty.getPreExtensionJSON() + "." + file.getExt();
                        } else {
                            outputFilePath = inputFilePath;
                        }

                        MinifyFileResult minifyFileResult = compress(inputFilePath, "text/x-json", outputFilePath, minifyProperty);
                        minifyResult.setInputJsonFilesSize(minifyResult.getInputJsonFilesSize() + minifyFileResult.getInputFileSize());
                        minifyResult.setOutputJsonFilesSize(minifyResult.getOutputJsonFilesSize() + minifyFileResult.getOutputFileSize());
                    }
                } catch (IOException ex) {
                    Exceptions.printStackTrace(ex);
                }
            }
        }

        minifyResult.setDirectories(directory);
        minifyResult.setCssFiles(cssFile);
        minifyResult.setJsFiles(jsFile);
        minifyResult.setHtmlFiles(htmlFile);
        minifyResult.setXmlFiles(xmlFile);
        minifyResult.setJsonFiles(jsonFile);

        return minifyResult;
    }

    public String compressSelectedJavaScript(String inputFilename, String content, MinifyProperty minifyProperty) throws IOException {
        Compiler compiler = new Compiler();
        CompilerOptions options = new CompilerOptions();

        compiler.initOptions(options);

        options.setStrictModeInput(false);
        options.setEmitUseStrict(false);
        options.setTrustedStrings(true);

        CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel(options);

        List<SourceFile> inputs = new ArrayList<SourceFile>();
        inputs.add(SourceFile.fromCode(inputFilename, content));

        StringWriter outputWriter = new StringWriter();

        compiler.compile(CommandLineRunner.getDefaultExterns(), inputs, options);
        outputWriter.flush();

        return compiler.toSource();
    }

    public void compressCssInternal(Reader in, Writer out, MinifyProperty minifyProperty) throws IOException {
        try {
            CssCompressor compressor = new CssCompressor(in);
            in.close();
            in = null;
            compressor.compress(out, minifyProperty.getLineBreakPosition());
            out.flush();
        } finally {
            IOUtils.closeQuietly(in);
            IOUtils.closeQuietly(out);
        }
    }

    public void compressHtmlInternal(Reader in, Writer out, MinifyProperty minifyProperty) throws IOException {
        try {
            HtmlCompressor compressor = new HtmlCompressor();
            compressor.setRemoveIntertagSpaces(true);
            compressor.setCompressCss(minifyProperty.isBuildInternalCSSMinify());               //compress inline css
            compressor.setCompressJavaScript(minifyProperty.isBuildInternalJSMinify());
            compressor.setYuiJsNoMunge(!minifyProperty.isJsObfuscate());
            String output = compressor.compress(fromStream(in));//out, minifyProperty.getLineBreakPosition());
            in.close();
            in = null;
            out.write(output);
            out.flush();
        } finally {
            IOUtils.closeQuietly(in);
            IOUtils.closeQuietly(out);
        }
    }

    public MinifyFileResult compressContent(String inputFilename, String content, String mimeType, String outputFilename, MinifyProperty minifyProperty) throws IOException {
        Writer out = null;
        MinifyFileResult minifyFileResult = new MinifyFileResult();
        Compiler compiler = new Compiler();
        CompilerOptions options = new CompilerOptions();

//        options.setLanguageOut(CompilerOptions.LanguageMode.ECMASCRIPT3);
        compiler.initOptions(options);

//        options.setStrictModeInput(false);
        options.setEmitUseStrict(false);
        options.setTrustedStrings(true);

        CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel(options);

        List<SourceFile> inputs = new ArrayList<SourceFile>();
        inputs.add(SourceFile.fromCode(inputFilename, content));

        try {
            File outputFile = new File(outputFilename);
            out = new OutputStreamWriter(new FileOutputStream(outputFile), minifyProperty.getCharset());

            String output;
            if (mimeType.equals("text/html")) {
                HtmlCompressor compressor = new HtmlCompressor();
                compressor.setRemoveIntertagSpaces(true);
                compressor.setCompressCss(minifyProperty.isBuildInternalCSSMinify());               //compress inline css
                compressor.setCompressJavaScript(minifyProperty.isBuildInternalJSMinify());        //compress inline javascript
                compressor.setYuiJsNoMunge(!minifyProperty.isJsObfuscate());
                output = compressor.compress(content);

                if (StringUtils.isBlank(MinifyProperty.getInstance().getHeaderHTML())) {
                    out.write(output);
                } else {
                    out.write(MinifyProperty.getInstance().getHeaderHTML() + "\n" + output);
                }
            } else if (mimeType.equals("text/javascript")) {
                compiler.compile(CommandLineRunner.getDefaultExterns(), inputs, options);

                if (StringUtils.isBlank(MinifyProperty.getInstance().getHeaderJS())) {
                    out.write(compiler.toSource());
                } else {
                    out.write(MinifyProperty.getInstance().getHeaderJS() + "\n" + compiler.toSource());
                }
            } else if (mimeType.equals("text/css")) {
                Reader in = new StringReader(content);
                CssCompressor compressor = new CssCompressor(in);
                in.close();
                StringWriter outputWriter = new StringWriter();
                compressor.compress(outputWriter, minifyProperty.getLineBreakPosition());
                outputWriter.flush();
                if (StringUtils.isBlank(MinifyProperty.getInstance().getHeaderCSS())) {
                    out.write(outputWriter.toString());
                } else {
                    out.write(MinifyProperty.getInstance().getHeaderCSS() + "\n" + outputWriter.toString());
                }
                outputWriter.close();
            } else if (mimeType.equals("text/x-json")) {
                JSONMinifyUtil compressor = new JSONMinifyUtil();
                output = compressor.minify(content);
                if (StringUtils.isBlank(MinifyProperty.getInstance().getHeaderJSON())) {
                    out.write(output);
                } else {
                    out.write(MinifyProperty.getInstance().getHeaderJSON() + "\n" + output);
                }
            } else if (mimeType.equals("text/xml-mime")) {
                XmlCompressor compressor = new XmlCompressor();
                compressor.setRemoveIntertagSpaces(true);
                compressor.setRemoveComments(true);
                compressor.setEnabled(true);
                output = compressor.compress(content);
                if (StringUtils.isBlank(MinifyProperty.getInstance().getHeaderXML())) {
                    out.write(output);
                } else {
                    out.write(MinifyProperty.getInstance().getHeaderXML() + "\n" + output);
                }
            }

            out.flush();
        } finally {
            IOUtils.closeQuietly(out);
        }
        return minifyFileResult;
    }

    public MinifyFileResult compress(String inputFilename, String mimeType, String outputFilename, MinifyProperty minifyProperty) throws IOException {
        InputStreamReader in = null;
        Writer out = null;
        MinifyFileResult minifyFileResult = new MinifyFileResult();
        Compiler compiler = new Compiler();
        CompilerOptions options = new CompilerOptions();
        
        compiler.initOptions(options);
        
        options.setEmitUseStrict(false);
        options.setTrustedStrings(true);
        
        CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel(options);

        List<SourceFile> inputs = new ArrayList<SourceFile>();
        inputs.add(SourceFile.fromFile(inputFilename));

        try {
            File inputFile = new File(inputFilename);
            File outputFile = new File(outputFilename);
            in = new InputStreamReader(new FileInputStream(inputFile), minifyProperty.getCharset());
            minifyFileResult.setInputFileSize(inputFile.length());
            out = new OutputStreamWriter(new FileOutputStream(outputFile), minifyProperty.getCharset());
            String output;

            if (mimeType.equals("text/html")) {
                HtmlCompressor compressor = new HtmlCompressor();
                compressor.setRemoveIntertagSpaces(true);
                compressor.setCompressCss(minifyProperty.isBuildInternalCSSMinify());               //compress inline css
                compressor.setCompressJavaScript(minifyProperty.isBuildInternalJSMinify());        //compress inline javascript
                compressor.setYuiJsNoMunge(!minifyProperty.isJsObfuscate());

                output = compressor.compress(fromStream(in));
                out.write(MinifyProperty.getInstance().getHeaderHTML() + "\n" + output);
            } else if (mimeType.equals("text/javascript")) {
                compiler.compile(CommandLineRunner.getDefaultExterns(), inputs, options);

                if (StringUtils.isBlank(MinifyProperty.getInstance().getHeaderJS())) {
                    out.write(compiler.toSource());
                } else {
                    out.write(MinifyProperty.getInstance().getHeaderJS() + "\n" + compiler.toSource());
                }
            } else if (mimeType.equals("text/css")) {
                CssCompressor compressor = new CssCompressor(in);
                StringWriter outputWriter = new StringWriter();
                compressor.compress(outputWriter, minifyProperty.getLineBreakPosition());
                outputWriter.flush();

                if (StringUtils.isBlank(MinifyProperty.getInstance().getHeaderJS())) {
                    out.write(outputWriter.toString());
                } else {
                    out.write(MinifyProperty.getInstance().getHeaderCSS() + "\n" + outputWriter.toString());
                }

                outputWriter.close();
            } else if (mimeType.equals("text/x-json")) {
                JSONMinifyUtil compressor = new JSONMinifyUtil();
                output = compressor.minify(fromStream(in));
                out.write(MinifyProperty.getInstance().getHeaderJSON() + "\n" + output);
            } else if (mimeType.equals("text/xml-mime")) {
                XmlCompressor compressor = new XmlCompressor();
                compressor.setRemoveIntertagSpaces(true);
                compressor.setRemoveComments(true);
                compressor.setEnabled(true);
                output = compressor.compress(fromStream(in));
                out.write(MinifyProperty.getInstance().getHeaderXML() + "\n" + output);
            }

            in.close();
            in = null;

            out.flush();

            minifyFileResult.setOutputFileSize(outputFile.length());
            if (minifyProperty.isAppendLogToFile()) {
                out.append("\n<!--Size: " + minifyFileResult.getInputFileSize() + "=>"
                        + minifyFileResult.getOutputFileSize() + "Bytes "
                        + "\n Saved " + minifyFileResult.getSavedPercentage() + "%-->");
            }

        } finally {
            IOUtils.closeQuietly(in);
            IOUtils.closeQuietly(out);
        }
        return minifyFileResult;
    }

    public void compressXmlInternal(Reader in, Writer out, MinifyProperty minifyProperty) throws IOException {
        try {
            XmlCompressor compressor = new XmlCompressor();
            compressor.setRemoveIntertagSpaces(true);
            compressor.setRemoveComments(true);
            compressor.setEnabled(true);
            String output = compressor.compress(fromStream(in));//out, minifyProperty.getLineBreakPosition());
            in.close();
            in = null;
            out.write(output);
            out.flush();
        } finally {
            IOUtils.closeQuietly(in);
            IOUtils.closeQuietly(out);
        }
    }

    public void compressJsonInternal(Reader in, Writer out, MinifyProperty minifyProperty) throws IOException {
        try {
            JSONMinifyUtil compressor = new JSONMinifyUtil();
            String output = compressor.minify(fromStream(in));
            in.close();
            in = null;
            out.write(output);
            out.flush();
        } finally {
            IOUtils.closeQuietly(in);
            IOUtils.closeQuietly(out);
        }
    }

    public Boolean isMinifiedFile(String fileName, String preExt) {
        String extension = "";

        int extIndex = fileName.lastIndexOf(".");
        int p = Math.max(fileName.lastIndexOf('/'), fileName.lastIndexOf('\\'));

        if (extIndex > p) {
            extension = fileName.substring(extIndex+1);
            preExt = preExt + "." + extension;
        }
        
        extIndex = fileName.lastIndexOf(preExt);
        
        return extIndex > p;
        
        // HINT: Old code.
//        String[] namePaths = fileName.split("\\" + preExt);
//        return (namePaths.length > 1 && namePaths[namePaths.length - 1].equals(preExt));
    }

    public static String fromStream(Reader in) throws IOException {
        StringBuilder srcsb = new StringBuilder();
        int c;
        while ((c = in.read()) != -1) {
            srcsb.append((char) c);
        }

        return srcsb.toString();
    }
}