package org.apache.ibatis.thread;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;

import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.Configuration;
import org.apache.log4j.Logger;
import org.springframework.core.NestedIOException;

import com.ejweb.conf.GConstants;

/**
 * 刷新使用进程
 * FROM: http://thinkgem.iteye.com/blog/2304557
 * @author liubaoquan
 */
public class MybatisRunnable implements java.lang.Runnable {

    private static final Logger LOG = Logger.getLogger(Runnable.class);
    private static final ThreadLocal<MybatisRunnable> LOCAL = new ThreadLocal<MybatisRunnable>();
    
    private String location;
    private Configuration configuration;

    private Long beforeTime = 0L; // 上一次刷新时间
    private static boolean refresh   = false; // 是否执行刷新
    private static boolean isRunning = false; // 是否执行刷新

    private static String mappingPath = GConstants.getValue("refresh.mappingPath", "/WEB-INF/classes/mapper/"); // xml文件夹匹配字符串，需要根据需要修改
    private static int delaySeconds   = GConstants.getIntValue("refresh.delaySeconds", 180);// 延迟刷新秒数
    private static int sleepSeconds   = GConstants.getIntValue("refresh.sleepSeconds", 60);// 休眠时间
    private static boolean enabled    = GConstants.getBoolean("refresh.enabled", false);// 是否允许对配置文件进行修改监听

    public static boolean isRefresh() {
        return refresh;
    }

    public static MybatisRunnable instance(final String location, final Configuration configuration){
        
        MybatisRunnable context = LOCAL.get();
        if (context == null) {
            context = new MybatisRunnable(location, configuration);
            LOCAL.set(context);
        }
        return context;
    }
    private MybatisRunnable(String location, Configuration configuration) {
        this.location = location.replaceAll("\\\\", "/");// 转换为Linux文件路径
        this.configuration = configuration;
    }

    @Override
    public void run() {
        this.location = this.location.substring("file [".length(),
                this.location.lastIndexOf(mappingPath) + mappingPath.length());
        this.beforeTime = System.currentTimeMillis();

//        LOG.debug("[location] " + location);
//        LOG.debug("[configuration] " + configuration);

        if (enabled && !isRunning) {
            isRunning = true;
            start(this);
        }
    }

    private void start(final MybatisRunnable runnable) {

        new Thread(new java.lang.Runnable() {

            @Override
            public void run() {

                try {
                    
                    Thread.sleep(delaySeconds * 1000);
                } catch (InterruptedException e2) {
                    e2.printStackTrace();
                }
                refresh = true;
                while (true) {
                    try {
                        LOG.debug("refresh location: " + location);
                        runnable.refresh(location, beforeTime);
                    } catch (Exception e1) {
                        e1.printStackTrace();
                    }

                    try {
                        Thread.sleep(sleepSeconds * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    /**
     * 执行刷新
     *
     * @param filePath   刷新目录
     * @param beforeTime 上次刷新时间
     * @throws NestedIOException     解析异常
     * @throws FileNotFoundException 文件未找到
     */
    private void refresh(String filePath, Long beforeTime) throws Exception {

//        Long refrehTime = System.currentTimeMillis();// 本次刷新时间
        this.beforeTime = System.currentTimeMillis();// 修改本次刷新时间
        List<File> refreshs = this.getRefreshFile(new File(filePath),
                beforeTime);
        if (refreshs.size() > 0) {
            LOG.info("refresh files:" + refreshs.size());
        }
        for (int i = 0; i < refreshs.size(); i++) {
            LOG.info("refresh file:" + refreshs.get(i).getAbsolutePath());
//            LOG.info("refresh filename:" + refreshs.get(i).getName());
            boolean status = this.parseMapper(new FileInputStream(refreshs.get(i)),
                    refreshs.get(i).getAbsolutePath());
            LOG.info("refresh status:" + status);
        }
        // 如果刷新了文件，则修改刷新时间，否则不修改
//        if (refreshs.size() > 0) {
//            this.beforeTime = refrehTime;
//        }
    }
    /**
     * 重新加载配置文件
     * 
     * @author renmb
     * @time  2017年4月11日
     * @param inputStream
     * @param resource
     * @param configuration
     * @return
     * @throws NestedIOException
     */
    private boolean parseMapper(java.io.InputStream inputStream, String resource)
            throws NestedIOException {// [mybatis-refresh][新增]刷新配置文件

        try {
            
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(inputStream, configuration, resource,
                    configuration.getSqlFragments());
            xmlMapperBuilder.parseMapper();
            return true;
        } catch (Exception e) {

            LOG.error("Failed to parse mapping resource: '" + resource + "'" + e.getMessage());
        } finally {

            ErrorContext.instance().reset();
        }
        return false;
    }
    /**
     * 获取需要刷新的文件列表
     *
     * @param dir        目录
     * @param beforeTime 上次刷新时间
     * @return 刷新文件列表
     */
    private List<File> getRefreshFile(File dir, Long beforeTime) {
        List<File> refreshs = new ArrayList<File>();

        File[] files = dir.listFiles();
        if(files == null || files.length == 0)
            return  refreshs;

        for (int i = 0; i < files.length; i++) {
            File file = files[i];
            if (file.isDirectory()) {
                refreshs.addAll(this.getRefreshFile(file, beforeTime));
            } else if (file.isFile() && file.getName().toLowerCase().endsWith(".xml")) {
                if (this.check(file, beforeTime)) {
                    refreshs.add(file);
                }
            } else {
                LOG.error("error file." + file.getAbsolutePath());
            }
        }

        return refreshs;
    }

    /**
     * 判断文件是否需要刷新
     *
     * @param file       文件
     * @param beforeTime 上次刷新时间
     * @return 需要刷新返回true，否则返回false
     */
    private boolean check(File file, Long beforeTime) {
//        if (file.lastModified() > beforeTime) {
//            return true;
//        }
        return file.lastModified() > beforeTime;
    }

}
