/*
 * Copyright (c) 2017.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.itfsw.mybatis.generator.plugins.tools;

import org.apache.ibatis.datasource.pooled.PooledDataSourceFactory;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.Context;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.exception.InvalidConfigurationException;
import org.mybatis.generator.exception.XMLParserException;
import org.mybatis.generator.internal.DefaultShellCallback;

import javax.sql.DataSource;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * ---------------------------------------------------------------------------
 *
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2017/7/4 16:14
 * ---------------------------------------------------------------------------
 */
public class MyBatisGeneratorTool {
    public final static String DAO_PACKAGE = "com.itfsw.mybatis.generator.plugins.dao";    // dao package
    private List<String> warnings;  // 提示信息
    private Configuration config;   // 配置信息
    private String targetProject;  // 目标
    private String targetPackage; // package

    /**
     * 创建
     * @param resource
     * @return
     */
    public static MyBatisGeneratorTool create(String resource) throws IOException, XMLParserException {
        MyBatisGeneratorTool tool = new MyBatisGeneratorTool();
        tool.warnings = new ArrayList<>();

        // MyBatisGenerator 创建
        ConfigurationParser cp = new ConfigurationParser(tool.warnings);
        tool.config = cp.parseConfiguration(Resources.getResourceAsStream(resource));
        // 修正配置目标
        tool.fixConfigToTarget();
        return tool;
    }

    /**
     * 执行MyBatisGenerator
     * @param before
     * @param callback
     * @return
     * @throws SQLException
     * @throws IOException
     * @throws InterruptedException
     */
    public MyBatisGenerator generate(IBeforeCallback before, AbstractShellCallback callback) throws Exception {
        before.run();
        callback.setTool(this);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null, null, null, true);
        return myBatisGenerator;
    }

    /**
     * 执行MyBatisGenerator
     * @param callback
     * @return
     * @throws SQLException
     * @throws IOException
     * @throws InterruptedException
     */
    public MyBatisGenerator generate(AbstractShellCallback callback) throws Exception {
       return this.generate(() -> {

       }, callback);
    }

    /**
     * 执行MyBatisGenerator
     * @param before
     * @return
     * @throws SQLException
     * @throws IOException
     * @throws InterruptedException
     */
    public MyBatisGenerator generate(IBeforeCallback before) throws Exception {
        before.run();
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, new DefaultShellCallback(true), warnings);
        myBatisGenerator.generate(null, null, null, false);
        return myBatisGenerator;
    }

    /**
     * 执行MyBatisGenerator(不生成文件)
     * @return
     * @throws SQLException
     * @throws IOException
     * @throws InterruptedException
     */
    public MyBatisGenerator generate() throws InvalidConfigurationException, InterruptedException, SQLException, IOException {
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, new DefaultShellCallback(true), warnings);
        myBatisGenerator.generate(null, null, null, false);
        return myBatisGenerator;
    }

    /**
     * 编译项目并返回 SqlSession
     * @return
     */
    public SqlSession compile() throws IOException, ClassNotFoundException {
        // 动态编译java文件
        String target = targetProject + targetPackage.replaceAll("\\.", "/");
        List<File> javaFiles = getGeneratedFiles(new File(target), ".java");
        compileJavaFiles(javaFiles);
        return getSqlSession();
    }

    /**
     * 获取目标目录的ClassLoader
     * @return
     */
    public ClassLoader getTargetClassLoader() throws MalformedURLException {
        return URLClassLoader.newInstance(new URL[]{
                new File(targetProject).toURI().toURL()
        });
    }

    /**
     * 获取SqlSession
     * @return
     * @throws IOException
     */
    public SqlSession getSqlSession() throws IOException, ClassNotFoundException {
        org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration();
        config.setCallSettersOnNulls(true); // 设计null调用setter方法
        config.setMapUnderscoreToCamelCase(true);   // 驼峰命名支持

        // 设置mapper
        config.addMappers(targetPackage);
        // 设置数据源,事务
        PooledDataSourceFactory dataSourceFactory = new PooledDataSourceFactory();
        dataSourceFactory.setProperties(DBHelper.properties);
        DataSource dataSource = dataSourceFactory.getDataSource();
        JdbcTransactionFactory transactionFactory = new JdbcTransactionFactory();

        Environment environment = new Environment("test", transactionFactory, dataSource);
        config.setEnvironment(environment);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(config);
        return sqlSessionFactory.openSession(true);
    }

    /**
     * 动态编译java文件
     * @param files
     */
    private void compileJavaFiles(List<File> files) {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

        //获取java文件管理类
        StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
        //获取java文件对象迭代器
        Iterable<? extends JavaFileObject> it = manager.getJavaFileObjectsFromFiles(files);
        //设置编译参数
        ArrayList<String> ops = new ArrayList<>();
        ops.add("-Xlint:unchecked");
        //获取编译任务
        JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, ops, null, it);
        //执行编译任务
        task.call();
    }

    /**
     * 获取指定后缀的文件
     * @param file
     * @return
     */
    private List<File> getGeneratedFiles(File file, String ext) {
        List<File> list = new ArrayList<>();
        if (file.exists()) {
            File[] files = file.listFiles();
            for (File childFile : files) {
                if (childFile.isDirectory()) {
                    list.addAll(getGeneratedFiles(childFile, ext));
                } else if (childFile.getName().endsWith(ext)) {
                    list.add(childFile);
                }
            }
        }
        return list;
    }

    /**
     * 修正配置到指定target
     */
    private void fixConfigToTarget() {
        this.targetProject = this.getClass().getClassLoader().getResource("").getPath();
        this.targetPackage = DAO_PACKAGE + ".s" + new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
        for (Context context : config.getContexts()) {
            context.getJavaModelGeneratorConfiguration().setTargetProject(targetProject);
            context.getJavaModelGeneratorConfiguration().setTargetPackage(targetPackage);
            context.getSqlMapGeneratorConfiguration().setTargetProject(targetProject);
            context.getSqlMapGeneratorConfiguration().setTargetPackage(targetPackage);
            context.getJavaClientGeneratorConfiguration().setTargetProject(targetProject);
            context.getJavaClientGeneratorConfiguration().setTargetPackage(targetPackage);
        }
    }

    /**
     * Getter method for property <tt>warnings</tt>.
     * @return property value of warnings
     * @author hewei
     */
    public List<String> getWarnings() {
        return warnings;
    }

    /**
     * Getter method for property <tt>config</tt>.
     * @return property value of config
     * @author hewei
     */
    public Configuration getConfig() {
        return config;
    }

    /**
     * Getter method for property <tt>targetPackage</tt>.
     * @return property value of targetPackage
     * @author hewei
     */
    public String getTargetPackage() {
        return targetPackage;
    }
}