package nl.astraeus.database;

import nl.astraeus.template.EscapeMode;
import nl.astraeus.template.SimpleTemplate;
import nl.astraeus.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * Date: 11/19/13
 * Time: 10:46 PM
 */
public class DdlMapping {
    private final static Logger logger = LoggerFactory.getLogger(DdlMapping.class);

    public static enum QueryTemplates {
        CREATE,
        CREATE_COLUMN,
        INSERT,
        UPDATE,
        DELETE,
        SELECT,
        SELECT_WHERE,
        SELECT_FROM
    }

    public static enum DatabaseDefinition {
        H2("H2", "h2", true),
        POSTGRESQL("PostgreSQL", "postgresql", false),
        MYSQL("MySql", "mysql", false);

        private String name;
        private String packageName;
        private boolean ddlInUppercase;

        private DatabaseDefinition(String name, String packageName, boolean ddlInUppercase) {
            this.name = name;
            this.packageName = packageName;
            this.ddlInUppercase = ddlInUppercase;
        }

        public String getName() {
            return name;
        }

        public String getPackageName() {
            return packageName;
        }

        public boolean ddlNamesInUppercase() {
            return ddlInUppercase;
        }
    }

    private static DdlMapping instance = new DdlMapping();

    public static DdlMapping get() {
        return instance;
    }

    private static Map<Class<?>, SimpleTemplate> ddlMapping;
    private static Map<Class<?>, Class<?>> primitiveToWrapper;
    private static Map<QueryTemplates, SimpleTemplate> queryTemplates;

    static {
        primitiveToWrapper = new HashMap<>();

        primitiveToWrapper.put(boolean.class, Boolean.class);
        primitiveToWrapper.put(byte.class, Byte.class);
        primitiveToWrapper.put(char.class, Character.class);
        primitiveToWrapper.put(double.class, Double.class);
        primitiveToWrapper.put(float.class, Float.class);
        primitiveToWrapper.put(int.class, Integer.class);
        primitiveToWrapper.put(long.class, Long.class);
        primitiveToWrapper.put(short.class, Short.class);
        primitiveToWrapper.put(void.class, Void.class);
    }

    private DatabaseDefinition database;
    private boolean executeDdlUpdates = false;

    public DdlMapping() {
        setDatabaseType(DatabaseDefinition.H2);
    }

    public void setDatabaseType(DatabaseDefinition definition) {
        database = definition;
        reload();
    }

    public void setExecuteDDLUpdates(boolean eddlup) {
        executeDdlUpdates = eddlup;
    }

    public boolean isExecuteDdlUpdates() {
        return executeDdlUpdates;
    }

    private String findSqlResource(String name) {
        try {
            InputStream in = DdlMapping.class.getResourceAsStream("sql/"+database.getPackageName()+"/"+name);

            if (in == null) {
                in = DdlMapping.class.getResourceAsStream("sql/def/"+name);
            }

            return Util.readAsString(in);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    private void reload() {
        ddlMapping = new HashMap<>();
        queryTemplates = new HashMap<>();

        try {
            String types = findSqlResource("types.sql");

            String [] lines = types.split("\\n");

            for (String line : lines) {
                String [] parts = line.split("\\=");

                if (parts.length != 2) {
                    logger.warn("Skipped line: ["+line+"] while loading mapping definition of types.sql");
                }

                Class cls = Class.forName(parts[0]);
                ddlMapping.put(cls, new SimpleTemplate("${", "}", EscapeMode.NONE, parts[1]));
            }
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e);
        }

        queryTemplates.put(QueryTemplates.CREATE,        new SimpleTemplate("${", "}", EscapeMode.NONE, findSqlResource("create.sql")));
        queryTemplates.put(QueryTemplates.CREATE_COLUMN, new SimpleTemplate("${", "}", EscapeMode.NONE, findSqlResource("createColumn.sql")));
        queryTemplates.put(QueryTemplates.INSERT,        new SimpleTemplate("${", "}", EscapeMode.NONE, findSqlResource("insert.sql")));
        queryTemplates.put(QueryTemplates.UPDATE,        new SimpleTemplate("${", "}", EscapeMode.NONE, findSqlResource("update.sql")));
        queryTemplates.put(QueryTemplates.DELETE,        new SimpleTemplate("${", "}", EscapeMode.NONE, findSqlResource("delete.sql")));
        queryTemplates.put(QueryTemplates.SELECT,        new SimpleTemplate("${", "}", EscapeMode.NONE, findSqlResource("select.sql")));
        queryTemplates.put(QueryTemplates.SELECT_WHERE,  new SimpleTemplate("${", "}", EscapeMode.NONE, findSqlResource("selectWhere.sql")));
        queryTemplates.put(QueryTemplates.SELECT_FROM,   new SimpleTemplate("${", "}", EscapeMode.NONE, findSqlResource("selectFrom.sql")));
    }

    public SimpleTemplate getDdlTemplateForType(Class<?> type) {
        if (type.isPrimitive()) {
            type = primitiveToWrapper.get(type);
        }

        // todo: check for null

        return ddlMapping.get(type);
    }

    public SimpleTemplate getBlobType() {
        return getDdlTemplateForType(Object.class);
    }

    public SimpleTemplate getIdType() {
        return getDdlTemplateForType(Long.class);
    }

    public SimpleTemplate getQueryTemplate(QueryTemplates type) {
        return queryTemplates.get(type);
    }

    public boolean ddlNamesInUppercase() {
        return database.ddlNamesInUppercase();
    }
}
