package com.fitbank.schemautils.tools;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.MessageFormat;
import java.util.jar.JarInputStream;
import java.util.prefs.Preferences;
import java.util.zip.ZipEntry;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

/**
 * Clase que mezcla archivos de script de actualizacion, en un solo archivo.
 *
 * @author Soft Warehouse S.A.
 */
public class UpdateScriptGenerator {

    public String fitbankUpdate = "";

    public String datosCamposParcial = "";

    public String datosContraintsParcial = "";

    public String changeset = "";

    public String changesetPattern = "{0}{1}{2}{3}";

    public String fitbankUpdatePattern = "fitbank{0}sinTS";

    public String datosCamposParcialPattern = "DatosCamposParcial{0}.sql";

    public String datosContraintsParcialPattern = "DatosCodConsParcial{0}.sql";

    public File outputFile = null;

    public File inputFolder = null;

    public Integer fromVersion = 0;

    public Integer toVersion = 0;

    private boolean fenicios = false;

    private String extraVersion = StringUtils.EMPTY;

    public static Preferences preferences = Preferences.userNodeForPackage(
            UpdateScriptGenerator.class);

    public static final String PREF_UPDATES_FOLDER_NAME = "in";

    public static final String OUTPUT_FILE_NAME = "startUpdateFitbank.sql";

    public static final Logger LOGGER = Logger.getRootLogger();

    public void readFolder(File folder) throws Exception {
        for (File file : folder.listFiles()) {
            if (this.isFenicios()) {
                this.readUpdateFiles(file.getParentFile());
                return;
            }

            if (file.isDirectory() && file.getName().equals(this.fitbankUpdate)) {
                this.readUpdateFiles(file);
                return;
            }

            if (file.getName().startsWith(this.fitbankUpdate)) {
                if (file.getName().endsWith(".zip") || file.getName().endsWith(".jar")) {
                    this.readUpdateZip(file);
                } else if (file.getName().startsWith(this.fitbankUpdate) && file.getName().endsWith("rar")) {
                    this.readUpdateRar(file);
                }

                return;
            }
        }
    }

    private void readUpdateFiles(File file) throws Exception {
        File fitbank_update_file = new File(file.getAbsolutePath() + "/" + this.fitbankUpdate.concat(".sql"));
        File datos_campos_parcial_file = new File(file.getAbsolutePath() + "/" + this.datosCamposParcial);
        File datos_constraints_parcial_file = new File(file.getAbsolutePath() + "/" + this.datosContraintsParcial);
        String contents;

        if (fitbank_update_file.exists()) {
            contents = new String(IOUtils.toByteArray(new FileInputStream(fitbank_update_file)), "UTF-8");
            this.appendToOutputFile(contents, true);
        } else if (!this.isFenicios()) {
            throw new Error("No se encontro el archivo " + this.fitbankUpdate.concat(".sql"));
        }

        if (datos_campos_parcial_file.exists()) {
            contents = new String(IOUtils.toByteArray(new FileInputStream(datos_campos_parcial_file)), "UTF-8");
            this.appendToOutputFile(contents, true);
        } else if (!this.isFenicios()) {
            throw new Error("No se encontro el archivo " + this.datosCamposParcial);
        }

        if (datos_constraints_parcial_file.exists()) {
            contents = new String(IOUtils.toByteArray(new FileInputStream(datos_constraints_parcial_file)), "UTF-8");
            this.appendToOutputFile(contents, true);
        } else if (!this.isFenicios()) {
            throw new Error("No se encontro el archivo " + this.datosContraintsParcial);
        }
    }

    private void readUpdateRar(File file) {
        try {
            String folderName = file.getName().replaceAll("\\..*", "");
            String folderPath = file.getParent() + File.separator + folderName;
            File folder = new File(file.getParent() + File.separator + folderName);
            folder.mkdir();

            String extractCommand = "unrar x -y " + file.getAbsolutePath() + " " + folderPath;
            Process p = Runtime.getRuntime().exec(extractCommand);
            p.waitFor();
            if (p.exitValue() != 0) {
                System.out.println("ERROR AL DESCOMPRIMIR EL ARCHIVO:\n" + this.getMessage(p.getErrorStream()));
                System.out.println("SUGERENCIA: Intente mejor descomprimir todos "
                        + "los archivos de actualizacion en sus respectivas carpetas, "
                        + "y vuelva a ejecutar la herramienta");
            }

            this.readUpdateFiles(new File(folderPath));
        } catch (IOException ioe) {
            LOGGER.error("Problemas al descomprimir un archivo rar", ioe);
        } catch (Exception ex) {
            LOGGER.error("Problemas al leer la carpeta con las actualizaciones", ex);
        }
    }

    /**
     * Obtener el error cuando se ejecuta desde la linea de comandos del
     * servidor.
     *
     * @param is
     * @return
     * @throws IOException
     */
    private String getMessage(InputStream is) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String response = "";
        String nachricht;
        while ((nachricht = br.readLine()) != null) {
            response += nachricht;
        }

        return response;
    }

    private void readUpdateZip(File file) {
        try {
            String fitbankUpdateEntry = null;
            String datosCamposParcialEntry = null;
            String datosConstraintsParcial = null;
            JarInputStream jarInputStream = new JarInputStream(new FileInputStream(file));
            ZipEntry zipEntry = jarInputStream.getNextEntry();
            while (zipEntry != null) {
                if (!zipEntry.isDirectory()) {
                    String entryName = zipEntry.getName().substring(zipEntry.getName().lastIndexOf("/") + 1);
                    String contents = new String(IOUtils.toByteArray(jarInputStream), "UTF-8");

                    if (this.fitbankUpdate.concat(".sql").equals(entryName)) {
                        fitbankUpdateEntry = contents;
                    }

                    if (this.datosCamposParcial.equals(entryName)) {
                        datosCamposParcialEntry = contents;
                    }

                    if (this.datosContraintsParcial.equals(entryName)) {
                        datosConstraintsParcial = contents;
                    }
                }

                if (fitbankUpdateEntry != null && datosCamposParcialEntry != null
                        && datosConstraintsParcial != null) {
                    this.appendToOutputFile(fitbankUpdateEntry, true);
                    this.appendToOutputFile(datosCamposParcialEntry, false);
                    this.appendToOutputFile(datosConstraintsParcial, false);
                    break;
                } else {
                    zipEntry = jarInputStream.getNextEntry();
                }
            }

            jarInputStream.close();
        } catch (Exception e) {
            LOGGER.error(e, e);
        }
    }

    private void appendToOutputFile(String contents, boolean newChangeSet) {
        try {
            FileWriter fw = new FileWriter(this.getOutputFile(), true);
            BufferedWriter bw = new BufferedWriter(fw);
            if (newChangeSet) {
                bw.append("/*======================================*/");
                bw.newLine();
                bw.append("/* COMPLETE SCRIPT FOR fitbank" + changeset + " */");
                bw.newLine();
                bw.append("/*======================================*/");
                bw.newLine();
                bw.newLine();
            }
            bw.append(contents);
            bw.newLine();
            bw.close();
            fw.close();
        } catch (Exception e) {
            LOGGER.error(e, e);
        }
    }

    public void setChangeset(String instalacion, String release, String subRelease,
            Integer fromVersion, Integer toVersion) {
        this.changesetPattern = MessageFormat.format(this.changesetPattern, instalacion, release,
                subRelease);
        this.setFromVersion(fromVersion);
        this.setToVersion(toVersion);
    }

    private void applyChangeSet(Integer version) {
        String sVersion = StringUtils.leftPad(String.valueOf(version), 3, "0").concat(this.extraVersion);
        this.changeset = MessageFormat.format(this.changesetPattern, null, null, null, sVersion);
        this.fitbankUpdate = MessageFormat.format(this.fitbankUpdatePattern, this.changeset);
        this.datosCamposParcial = MessageFormat.format(this.datosCamposParcialPattern, this.changeset);
        this.datosContraintsParcial = MessageFormat.format(this.datosContraintsParcialPattern, this.changeset);
    }

    public void setOutputFile(File output) {
        this.outputFile = output;
    }

    public void setInputFolder(File input) {
        this.inputFolder = input;
    }

    public File getOutputFile() {
        return this.outputFile;
    }

    public File getInputFolder() {
        return this.inputFolder;
    }

    public void setFromVersion(Integer fromVersion) {
        this.fromVersion = fromVersion;
    }

    public void setToVersion(Integer toVersion) {
        this.toVersion = toVersion;
    }

    public Integer getFromVersion() {
        return this.fromVersion;
    }

    public Integer getToVersion() {
        return this.toVersion;
    }

    public boolean isFenicios() {
        return fenicios;
    }

    public void setFenicios(boolean fenicios) {
        this.fenicios = fenicios;
        this.fitbankUpdatePattern = fenicios ? "ActualizacionFenicios{0}" : "fitbank{0}sinTS";
    }

    public void setExtraVersion(String extraVersion) {
        this.extraVersion = extraVersion;
    }

    public void run() {
        try {
            this.getOutputFile().delete();
            this.getOutputFile().createNewFile();
        } catch (Exception e) {
            LOGGER.error(e, e);
        }

        for (int i = this.getFromVersion(); i <= this.getToVersion(); i++) {
            try {
                this.applyChangeSet(i);
                this.readFolder(this.getInputFolder());
            } catch (Exception e) {
                LOGGER.error("No se pudo generar el scipt", e);
                break;
            }
        }
    }

    public static void main(String[] args) {
        File in = new File(preferences.get(PREF_UPDATES_FOLDER_NAME, ""));

        JFileChooser chooser = new JFileChooser();
        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        chooser.setCurrentDirectory(in);
        chooser.setSelectedFile(in);
        chooser.setDialogTitle("Ubicación de los archivos de actualizacion FitBank (carpeta /repo-persistence/sql/modelo/oracle/");

        if (chooser.showOpenDialog(null) == JFileChooser.CANCEL_OPTION) {
            return;
        }

        in = chooser.getSelectedFile();
        preferences.put(PREF_UPDATES_FOLDER_NAME, in.getAbsolutePath());

        String fromVersion = JOptionPane.showInputDialog(null,
                new Object[]{new JLabel("Versión inicial de creación del script (únicamente los últimos 3 dígitos) ejm: 097")},
                "Escriba la version inicial del script",
                JOptionPane.DEFAULT_OPTION);

        String toVersion = JOptionPane.showInputDialog(null,
                new Object[]{new JLabel("Versión final de creación del script (únicamente los últimos 3 dígitos) ejm: 101")},
                "Escriba la version final del script",
                JOptionPane.DEFAULT_OPTION);

        if (StringUtils.isBlank(fromVersion) || StringUtils.isBlank(toVersion)) {
            System.out.println("ERROR: Favor, ingrese un valor para las versiones desde/hasta");
            return;
        }

        if (!StringUtils.isNumeric(fromVersion) || !StringUtils.isNumeric(toVersion)) {
            System.out.println("ERROR: Favor, ingrese un valor numérico para las versiones desde/hasta");
            return;
        }

        UpdateScriptGenerator usg = new UpdateScriptGenerator();

        //Para FitBank
        usg.setInputFolder(in);
        usg.setOutputFile(new File(in.getAbsolutePath() + File.separator + "startUpdateFitbank.sql"));
        usg.setChangeset("2", "01", "067", Integer.valueOf(fromVersion), Integer.valueOf(toVersion));

        //Para SQLServer
        //usg.setInputFolder(new File("/media/DATA/fitbank/desarrolloPersistence/sql/modelo/sqlserver"));
        //usg.setOutputFile(new File("/media/DATA/fitbank/desarrolloPersistence/sql/modelo/sqlserver/startUpdateFitbank.sql"));
        //usg.setChangeset("2","01","067", 67, 94);
        //usg.setExtraVersion("SQL");
        //Para Fenicios
        //usg.setInputFolder(new File("/media/DATA/fitbank/desarrolloPersistenceFenicios/sql/modelo/oracle"));
        //usg.setOutputFile(new File("/media/DATA/fitbank/desarrolloPersistenceFenicios/sql/modelo/oracle/startUpdateFitbank.sql"));
        //usg.setChangeset("2","02","001", 137, 140);
        //usg.setFenicios(true);
        //Ejecutar proceso
        usg.run();
    }
}
