package me.chyxion.tigon.codegen.service.support;

import me.chyxion.tigon.mybatis.Table;
import me.chyxion.tigon.service.BaseCrudService;
import me.chyxion.tigon.service.BaseService;
import me.chyxion.tigon.service.support.BaseCrudServiceSupport;
import me.chyxion.tigon.util.WordUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import me.chyxion.tigon.codegen.controller.CodeGenController.GenForm;
import me.chyxion.tigon.codegen.service.CodeGenBaseTool;
import me.chyxion.tigon.codegen.service.CodeGenService;
import me.chyxion.tigon.codegen.service.CodeGenerator;
import me.chyxion.tigon.mybatis.BaseMapper;
import me.chyxion.tigon.model.M0;
import me.chyxion.tigon.model.M1;
import me.chyxion.tigon.model.M2;
import me.chyxion.tigon.model.M3;
import me.chyxion.tigon.webmvc.test.ControllerTestTool;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * @version 0.0.1
 * @since 0.0.1
 * @author Shaun Chyxion <br>
 * chyxion@163.com <br>
 * Oct 6, 2014 1:10:02 PM
 */
public class CodeGenServiceSupport implements CodeGenService {
    private static final Logger log = LoggerFactory.getLogger(CodeGenServiceSupport.class);
    private static final Map<String, String> TYPE_PACKAGES = new HashMap<String, String>() {
        private static final long serialVersionUID = 1L;
        {
            put(Date.class.getSimpleName(), Date.class.getName());
        }
    };

    @Autowired
    private CodeGenBaseTool baseTool;
    @Autowired
    private List<CodeGenerator> codeGen;

    /**
     * {@inheritDoc}
     */
    @Override
    @SuppressWarnings("unchecked")
    public void process(String module, String model, List<?> columns, String table, GenForm form) {
        String minusJoinedModelName = WordUtils.convertCamelCase(model, "-").toLowerCase();
        String mappingUrl = minusJoinedModelName;
        String pkg = form.getPkg();
        if (StringUtils.isBlank(pkg)) {
            pkg = baseTool.getPkg();
        }
        if (StringUtils.isNotBlank(module)) {
            mappingUrl = module + "/" + mappingUrl;
            pkg += "." + module;
        }
        else {
            module = "";
        }

        model = StringUtils.capitalize(model);
        Map<String, Object> fmDataModel = new HashMap<String, Object>();
        fmDataModel.put("genController", form.isGenController());
        fmDataModel.put("genService", form.isGenService());
        fmDataModel.put("genMapper", form.isGenMapper());
        fmDataModel.put("genModel", form.isGenModel());
        fmDataModel.put("genTable", form.isGenTable());
        fmDataModel.put("genView", form.isGenView());
        fmDataModel.put("createTable", form.isCreateTable());

        fmDataModel.put("table", table.toLowerCase());
        fmDataModel.put("notNull", false);
        fmDataModel.put("notBlank", false);
        fmDataModel.put("cacheEnabled", baseTool.getConfig("cache.enabled", false));
        fmDataModel.put("useGeneratedKeys", false);

        // collect col names
        String idType = null;
        List<String> colNames = new ArrayList<String>(columns.size());
        for (Map<String, Object> col : (List<Map<String, Object>>) columns) {
            String colName = ((String) col.get("col")).toLowerCase();
            colNames.add(colName);
            if (M0.ID.equals(colName)) {
                    String javaType = (String) col.get("javaType");
                    if (ArrayUtils.contains(
                        new String[] {"int", "long", "boolean", "double", "byte"}, 
                            javaType)) {
                        javaType = StringUtils.capitalize(javaType);
                    }
                    idType = javaType;
                }
        }
        String baseModelName = null;
        String baseModelFullName = null;
        Collection<String> baseCols = null;
        if (colNames.containsAll(new M3<String, String>().cols())) {
            baseModelName = M3.class.getSimpleName() +
                "<" + idType + ", " + idType + ">";
            baseModelFullName = M3.class.getName();
            baseCols = new M3<String, String>().cols();
        }
        else if (colNames.containsAll(new M2<String>().cols())) {
            baseModelName = M2.class.getSimpleName() + "<" + idType + ">";
            baseModelFullName = M2.class.getName();
            baseCols = new M2<String>().cols();
        }
        else if (colNames.containsAll(new M1<String>().cols())) {
            baseModelName = M1.class.getSimpleName() + "<" + idType + ">";
            baseModelFullName = M1.class.getName();
            baseCols = new M1<String>().cols();
        }
        else {
            baseModelName = M0.class.getSimpleName();
            baseModelFullName = M0.class.getName();
            baseCols = new M0<String>().cols();
        }
        // custom config
        String cfgBaseModelName = baseTool.getConfig("super.base.model.name");
        if (StringUtils.isNotBlank(cfgBaseModelName)) {
            baseCols = baseTool.getBaseCols();
        }

        String cfgBaseModelFullName = 
            baseTool.getConfig("super.base.model.full.name");

        fmDataModel.put("baseModelName", 
            baseTool.getConfig("super.base.model.name", baseModelName));

        fmDataModel.put("baseModelFullName", 
            "NONE".equalsIgnoreCase(cfgBaseModelFullName) ? null : 
                StringUtils.isNotBlank(cfgBaseModelFullName) ? 
                    cfgBaseModelFullName : baseModelFullName);

        fmDataModel.put("tableAnnotationClassName", Table.class.getName());
        fmDataModel.put("tableAnnotationName", Table.class.getSimpleName());

        Set<String> imports = new HashSet<String>();
        List<Map<String, Object>> colsWithoutBase = 
            (List<Map<String, Object>>) columns;
        Iterator<Map<String, Object>> colIt = 
            colsWithoutBase.iterator();

        while (colIt.hasNext()) {
            Map<String, Object> col = colIt.next();
            // col name
            String colName = ((String) col.get("col")).toLowerCase();
            if (!baseCols.contains(colName)) {
                // prop name
                String propName = (String) col.get("name");
                // for setName
                col.put("Name", StringUtils.capitalize(propName));
                if ((Boolean) col.get("notNull")) {
                    if ("String".equals(col.get("javaType"))) {
                        fmDataModel.put("notBlank", true);
                    }
                    else {
                        fmDataModel.put("notNull", true);
                    }
                }
                // imports
                String p = TYPE_PACKAGES.get(col.get("javaType"));
                if (StringUtils.isNotBlank(p)) {
                    imports.add(p);
                }
            }
            // remove base col
            else {
                colIt.remove();
            }
        }

        fmDataModel.put("idType", idType);
        fmDataModel.put("cols", colsWithoutBase);
        fmDataModel.put("imports", imports);
        fmDataModel.put("url", mappingUrl);
        fmDataModel.put("pkg", pkg);
        fmDataModel.put("pkgDir", pkg.replace('.', '/'));
        fmDataModel.put("modelFullName", pkg + "." + model);
        fmDataModel.put("module", module);
        fmDataModel.put("ModelName", model);
        fmDataModel.put("modelName", StringUtils.uncapitalize(model));
        fmDataModel.put("minusJoinedModelName", minusJoinedModelName);
        /*
        // super classes name
        fmDataModel.put("baseControllerName", BaseController.class.getSimpleName());
        fmDataModel.put("baseControllerFullName", BaseController.class.getName());
        */

        // service interface
        fmDataModel.put("baseServiceName", BaseCrudService.class.getSimpleName());
        fmDataModel.put("baseServiceFullName", BaseService.class.getName());
        // server shiro
        fmDataModel.put("baseServiceSupportName", BaseCrudServiceSupport.class.getSimpleName());
        fmDataModel.put("baseServiceSupportFullName", BaseCrudServiceSupport.class.getName());

        // model
        // 
        // mapper
        fmDataModel.put("baseMapperName", BaseMapper.class.getSimpleName());
        fmDataModel.put("baseMapperFullName", BaseMapper.class.getName());

//        fmDataModel.put("mockMapperName", MockMapper.class.getSimpleName());
//        fmDataModel.put("mockMapperFullName", MockMapper.class.getName());
        // fmDataModel.put("mockName", Mock.class.getSimpleName());
        // fmDataModel.put("mockFullName", Mock.class.getName());

        fmDataModel.put("ctrlrTestToolName", ControllerTestTool.class.getSimpleName());
        fmDataModel.put("ctrlrTestToolFullName", ControllerTestTool.class.getName());
        // Object Doc
        Map<String, Object> objDocModel = new HashMap<String, Object>();
        // time now
        objDocModel.put("now", 
            DateFormat.getDateTimeInstance(DateFormat.DEFAULT, 
                DateFormat.DEFAULT, Locale.US).format(new Date()));
        // code doc
        fmDataModel.put("objDoc", 
            baseTool.renderFtl("/codegen/obj-doc.ftl", objDocModel));
        List<String> files = new LinkedList<String>();
        for (CodeGenerator gen : codeGen) {
            if (gen.accept(fmDataModel)) {
                String genResult = gen.process(fmDataModel, module, model);
                if (StringUtils.isNotBlank(genResult)) {
                    files.addAll(Arrays.asList(genResult.split(";")));
                }
            }
        }
        log.info("Save Code Generated Data, Module [{}], Model [{}].", module, model);
        Map<String, Object> mapRec = new HashMap<String, Object>();
        mapRec.put("cols", columns);
        mapRec.put("module", module);
        mapRec.put("model", model);
        mapRec.put("files", files);
        mapRec.put("table", table);
        baseTool.saveRecord(mapRec);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<?> list() {
        List<Map<String, Object>> items = baseTool.listAll();
        for (Map<String, Object> item : items) {
            String text = (String) item.get("model");
            String module = (String) item.get("module");
            if (StringUtils.isNotBlank(module)) {
                text = module + "/" + text;
            }
            item.put("text", text);
            item.put("checked", false);
            item.put("leaf", true);
            item.remove("files");
        }
        return items;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @SuppressWarnings("unchecked")
    public void delete(List<?> items, boolean dropTable) {
        for (Map<String, Object> item : (List<Map<String, Object>>) items) {
            baseTool.deleteRec((String) item.get("module"), (String) item.get("model"), dropTable);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Map<String, Object>> tables() {
        return baseTool.tables();
    }
}
