/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package br.com.ctecinf.orm;

import br.com.ctecinf.Utils;
import br.com.ctecinf.Database;
import java.io.File;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.TreeMap;

/**
 *
 * @author Cássio Conceição
 * @since 08/08/2019
 * @version 1908
 * @see http://ctecinf.com.br/
 */
public class CodeGenerate {

    /**
     * Gera pacote model na raíz da chamada no diretório "src"
     *
     *
     * @throws Exception
     */
    public static void model() throws Exception {
        model(null, false);
    }

    /**
     * Gera pacote model no caminho específicado
     *
     * @param path Caminho para o diretório "src" com as classes modelos
     * @throws Exception
     */
    public static void model(String path) throws Exception {
        model(path, false);
    }

    /**
     * Gera pacote model no caminho específicado
     *
     * @param path Caminho para o diretório "src" com as classes modelos
     * @param replaceExist reescrever classes existentes TRUE | FALSE
     * @throws Exception
     */
    public static void model(String path, boolean replaceExist) throws Exception {

        String packModel = "br.com.ctecinf.model";

        if (path == null || path.isEmpty()) {
            path = "src";
        }

        if (path.endsWith(File.separator)) {
            path = path.substring(0, path.length() - 1);
        }

        path += File.separator + packModel.replace(".", File.separator);

        try (Connection conn = Database.openConnection(); ResultSet rs = conn.getMetaData().getTables(null, null, "%", new String[]{"TABLE"})) {

            while (rs.next()) {

                String tableName = rs.getString(3).toLowerCase().trim();
                String className = Database.database2Java(tableName, true);

                SimpleDateFormat dtCreate = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
                SimpleDateFormat dtVersion = new SimpleDateFormat("yyyyMM");

                String getSets = "";
                String toString = "";

                String content = "/*\n"
                        + " * To change this license header, choose License Headers in Project Properties.\n"
                        + " * To change this template file, choose Tools | Templates\n"
                        + " * and open the template in the editor.\n"
                        + " */\n"
                        + "package " + packModel + ";\n"
                        + "\n"
                        + "import br.com.ctecinf.orm.Model;\n"
                        + "import br.com.ctecinf.orm.Table;\n"
                        + "import br.com.ctecinf.orm.Column;\n"
                        + "import java.sql.Types;\n"
                        + "\n"
                        + "/**\n"
                        + " *\n"
                        + " * @author Cássio Conceição\n"
                        + " * @since " + dtCreate.format(new Date()) + "\n"
                        + " * @version " + dtVersion.format(new Date()) + "\n"
                        + " * @see http://ctecinf.com.br/\n"
                        + " */\n"
                        + "@Table(\"" + rs.getString(3).toLowerCase().trim() + "\")\n"
                        + "public class " + className + " extends Model {\n"
                        + "\n";

                String columnId = null;
                Map<String, String> columnsFk = new TreeMap();

                try (ResultSet rsCols = conn.getMetaData().getPrimaryKeys(conn.getCatalog(), conn.getSchema(), tableName)) {
                    if (rsCols.next()) {
                        columnId = rsCols.getString(4).toLowerCase().trim();
                    }
                }

                try (ResultSet rsCols = conn.getMetaData().getImportedKeys(conn.getCatalog(), conn.getSchema(), tableName)) {
                    while (rsCols.next()) {
                        columnsFk.put(rsCols.getString(8).toLowerCase().trim(), rsCols.getString(3).toLowerCase().trim());
                    }
                }

                try (ResultSet rsCols = conn.getMetaData().getColumns(conn.getCatalog(), conn.getSchema(), tableName, "%")) {

                    while (rsCols.next()) {

                        String columnName = rsCols.getString(4).toLowerCase().trim();
                        String fieldName = Database.database2Java(columnName, false);

                        if (columnName.endsWith("_id")) {
                            fieldName = Database.database2Java(columnName.substring(0, columnName.length() - 3), false);
                        }

                        String type = Database.type2Class(rsCols.getInt(5)).getName();
                        String columnType = ", type = " + CodeGenerate.getType(rsCols.getInt(5));

                        String label = ", label = \"" + CodeGenerate.getLabel(columnName.replace("_", " ")) + "\"";

                        boolean notNull = !rsCols.getString(18).equalsIgnoreCase("yes");

                        if (type.contains("java.lang.")) {
                            type = type.replace("java.lang.", "");
                        }

                        if (!columnName.equalsIgnoreCase(columnId) && !columnsFk.keySet().contains(columnName)) {

                            content += "    @Column(name = \"" + columnName + "\"" + columnType + (notNull ? ", isNotNull = true" : "") + label + ")\n"
                                    + "    private " + type + " " + fieldName + ";\n\n";

                            if (toString.isEmpty() && rsCols.getInt(5) == Types.VARCHAR) {

                                toString = "    @Override\n"
                                        + "    public String toString() {\n"
                                        + "        return " + fieldName + " == null ? \"" + className + " {\" + (getId() == null ? \"Não cadastrado\" : \"ID = \" + getId()) + \"}\" : " + fieldName + ";\n"
                                        + "    }\n";
                            }

                            getSets += "    public " + type + " get" + Database.database2Java(fieldName, true) + "() {\n"
                                    + "        return this." + fieldName + ";\n"
                                    + "    }\n"
                                    + "\n"
                                    + "    public void set" + Database.database2Java(fieldName, true) + "(" + type + " " + fieldName + ") {\n"
                                    + "        this." + fieldName + " = " + fieldName + ";\n"
                                    + "    }\n\n";

                        } else if (!columnName.equalsIgnoreCase(columnId) && columnsFk.keySet().contains(columnName)) {

                            content += "    @Column(name = \"" + columnName + "\"" + columnType + (notNull ? ", isNotNull = true" : "") + label + ", join = " + Database.database2Java(columnsFk.get(columnName), true) + ".class)\n"
                                    + "    private " + Database.database2Java(columnsFk.get(columnName), true) + " " + fieldName + ";\n\n";

                            if (toString.isEmpty()) {

                                toString = "    @Override\n"
                                        + "    public String toString() {\n"
                                        + "        return " + fieldName + " == null ? \"" + className + " {\" + (getId() == null ? \"Não cadastrado\" : \"ID = \" + getId()) + \"}\" : " + fieldName + ".toString();\n"
                                        + "    }\n";
                            }

                            getSets += "    public " + Database.database2Java(columnsFk.get(columnName), true) + " get" + Database.database2Java(fieldName, true) + "() {\n"
                                    + "        return this." + fieldName + ";\n"
                                    + "    }\n"
                                    + "\n"
                                    + "    public void set" + Database.database2Java(fieldName, true) + "(" + Database.database2Java(columnsFk.get(columnName), true) + " " + fieldName + ") {\n"
                                    + "        this." + fieldName + " = " + fieldName + ";\n"
                                    + "    }\n\n";
                        }
                    }
                }

                content += getSets + toString + "}";

                File f = new File(path + File.separator + className + ".java");

                if (f.exists() && replaceExist) {
                    Utils.writeFile(path + File.separator + className + ".java", content, false);
                } else if (!f.exists()) {
                    Utils.writeFile(path + File.separator + className + ".java", content, false);
                }
            }
        }
    }

    private static String getLabel(String name) {

        String str = "";
        boolean upper = true;

        for (char c : name.toCharArray()) {

            if (c == ' ') {
                upper = true;
                str += ' ';
            } else if (upper) {
                str += String.valueOf(c).toUpperCase();
                upper = false;
            } else {
                str += String.valueOf(c).toLowerCase();
            }
        }

        return str.replace(" Id", "");
    }

    private static String getType(int type) {

        switch (type) {

            case Types.BIGINT:
                return "Types.BIGINT";

            case Types.BINARY:
                return "Types.BOOLEAN";

            case Types.BIT:
                return "Types.BOOLEAN";

            case Types.BLOB:
                return "Types.BLOB";

            case Types.BOOLEAN:
                return "Types.BOOLEAN";

            case Types.CHAR:
                return "Types.VARCHAR";

            case Types.DATE:
                return "Types.DATE";

            case Types.DECIMAL:
                return "Types.DECIMAL";

            case Types.DOUBLE:
                return "Types.DOUBLE";

            case Types.FLOAT:
                return "Types.FLOAT";

            case Types.INTEGER:
                return "Types.INTEGER";

            case Types.LONGNVARCHAR:
                return "Types.VARCHAR";

            case Types.LONGVARCHAR:
                return "Types.BLOB";

            case Types.NCHAR:
                return "Types.VARCHAR";

            case Types.NVARCHAR:
                return "Types.VARCHAR";

            case Types.SMALLINT:
                return "Types.BOOLEAN";

            case Types.TIME:
                return "Types.TIME";

            case Types.TIMESTAMP:
                return "Types.TIMESTAMP";

            case Types.TINYINT:
                return "Types.BOOLEAN";

            case Types.VARCHAR:
                return "Types.VARCHAR";

            default:
                return "Types.VARCHAR";
        }
    }

}
