package com.rthoni.intellij.codefromds.business;

import com.intellij.database.model.DasForeignKey;
import com.intellij.database.model.MultiRef;
import com.intellij.database.psi.DbDataSource;
import com.intellij.database.util.DasUtil;
import com.intellij.openapi.project.Project;
import com.rthoni.intellij.codefromds.dbo.options.ColumnSelection;
import com.rthoni.intellij.codefromds.dbo.options.GenerateOptions;
import com.rthoni.intellij.codefromds.dbo.options.TableSelection;
import com.rthoni.intellij.codefromds.dbo.template.ColumnDataSourceDbo;
import com.rthoni.intellij.codefromds.dbo.template.DataSourceDbo;
import com.rthoni.intellij.codefromds.dbo.template.ForeignKeyDbo;
import com.rthoni.intellij.codefromds.dbo.template.TableDataSourceDbo;
import org.json.JSONObject;
import org.jtwig.JtwigModel;
import org.jtwig.JtwigTemplate;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
 * Created by robin on 11/15/16.
 */
public abstract class Generator {

    public static void saveOptions(GenerateOptions options) throws Exception
    {
        HashMap<String, Object> map = options.toMap();
        JSONObject obj = new JSONObject(map);
        FileOutputStream file = new FileOutputStream(options.getConfigAbsolutePath());
        file.write(obj.toString(4).getBytes());
        file.close();
    }

    public static GenerateOptions loadOptions(String configPath) throws Exception
    {
        String data = Files.readAllLines(Paths.get(configPath), StandardCharsets.UTF_8).stream().reduce("", (s1, s2) -> s1 + s2 + "\n");

        JSONObject obj = new JSONObject(data);
        String src = obj.getJSONObject("selection").getString("source");

        DbDataSource dataSource = Helper.findDataSource(src);
        if (dataSource == null) {
            throw new Exception("Data source " + src + " not found");
        }

        GenerateOptions options = new GenerateOptions(dataSource);
        options.setConfigAbsolutePath(configPath);
        options.fromJson(obj);
        return options;
    }

    public static ForeignKeyDbo convertForeignKey(DasForeignKey fk, DataSourceDbo source)
    {
        ForeignKeyDbo dbo = new ForeignKeyDbo();

        dbo.setName(fk.getName());

        dbo.setSourceForeignKeyName("fk_" + fk.getRefTable().getName());
        dbo.setSourceTable(source.getTables().stream().filter(t -> t.getName().equals(fk.getTable().getName())).findFirst().get());
        MultiRef columnsRef = fk.getColumnsRef();
        dbo.setSourceColumns(dbo.getSourceTable().getColumns().stream().filter(c -> StreamSupport.stream(columnsRef.names().spliterator(), false).anyMatch(cc -> cc.equals(c.getName()))).collect(Collectors.toList()));

        dbo.setTargetForeignKeyName(fk.getTable().getName() + "_fk");
        dbo.setTargetTable(source.getTables().stream().filter(t -> t.getName().equals(fk.getRefTable().getName())).findFirst().get());
        MultiRef refColumns = fk.getRefColumns();
        dbo.setTargetColumns(dbo.getTargetTable().getColumns().stream().filter(c -> StreamSupport.stream(refColumns.names().spliterator(), false).anyMatch(cc -> cc.equals(c.getName()))).collect(Collectors.toList()));

        dbo.getTargetTable().getTargetForeignKeys().add(dbo);

        return dbo;
    }

    public static ColumnDataSourceDbo convertColumn(ColumnSelection columnSelection)
    {
        ColumnDataSourceDbo dbo = new ColumnDataSourceDbo();
        dbo.setName(columnSelection.getColumn().getName());
        dbo.setPrimary(DasUtil.isPrimary(columnSelection.getColumn()));
        dbo.setSelected(columnSelection.isSelected());
        return dbo;
    }

    public static TableDataSourceDbo convertTable(TableSelection tableSelection)
    {
        TableDataSourceDbo dbo = new TableDataSourceDbo();
        dbo.setName(tableSelection.getTable().getName());
        dbo.setColumns(tableSelection.getColumns().stream().map(Generator::convertColumn).collect(Collectors.toList()));
        dbo.setPrimaryKeys(dbo.getColumns().stream().filter(ColumnDataSourceDbo::isPrimary).collect(Collectors.toList()));
        dbo.setHasAny(!tableSelection.hasNone());
        dbo.setTargetForeignKeys(new Vector<>());
        return dbo;
    }

    public static DataSourceDbo convertOptions(GenerateOptions options)
    {
        DataSourceDbo dbo = new DataSourceDbo();

        dbo.setName(options.getSelection().getSource().getName());
        dbo.setTables(options.getSelection().getTables().stream().map(Generator::convertTable).collect(Collectors.toList()));

        List<TableDataSourceDbo> tables = dbo.getTables();
        List<TableSelection> tableSelections = options.getSelection().getTables();
        for (int i = 0; i < tables.size(); ++i) {
            TableDataSourceDbo table = tables.get(i);
            TableSelection tableSelection = tableSelections.get(i);
            table.setSourceForeignKeys(DasUtil.getForeignKeys(tableSelection.getTable()).toList().stream().map(t -> convertForeignKey(t, dbo)).collect(Collectors.toList()));
        }

        return dbo;
    }

    public static void generateFile(String templatePath, String outputFile, DataSourceDbo dataSource, TableDataSourceDbo table) throws IOException
    {
        String data = Helper.readFile(templatePath);

        FileOutputStream file = new FileOutputStream(outputFile);

        JtwigTemplate template = JtwigTemplate.inlineTemplate(data);
        JtwigModel model = JtwigModel.newModel().with("dataSource", dataSource).with("table", table);

        template.render(model, file);

        file.close();
    }

    public static void generate(GenerateOptions options, Project project) throws IOException
    {
        String modelsAbsolutePath = Helper.getAbsolutePath(project, options.getModelsRelativePath());
        String dataSourceTemplateAbsolutePath = Helper.getAbsolutePath(project, options.getDataSourceTemplateRelativePath());
        String modelsTemplateAbsolutePath = Helper.getAbsolutePath(project, options.getModelsTemplateRelativePath());

        DataSourceDbo dbo = convertOptions(options);

        generateFile(dataSourceTemplateAbsolutePath, modelsAbsolutePath + File.separator +
                "Database." + options.getFilesExtension(), dbo, null);
        for (TableDataSourceDbo table : dbo.getTables()) {
            if (table.hasAny()) {
                generateFile(modelsTemplateAbsolutePath, modelsAbsolutePath + File.separator +
                        table.getName() + "." + options.getFilesExtension(), dbo, table);
            }
        }
    }
}
