package com.ruoyi.common.dev;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import com.ruoyi.common.utils.StringUtils;
import lombok.Data;
import lombok.experimental.Accessors;

import java.io.File;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

@Data
@Accessors(chain = true)
public class ExtractGeneratedSource
{
    private String zipPath;
    private String javaProjectPath;
    private String webProjectPath;
    private String module;
    private Map<String, String> fileMap = new LinkedHashMap<>();

    private ZipFile zipFile;

    public void Set(String zipPath, String javaProjectPath, String webProjectPath, String module)
    {
        this.zipPath = zipPath;
        this.javaProjectPath = javaProjectPath;
        this.webProjectPath = webProjectPath;
        this.module = module;
    }

    public void Reset()
    {
        if(null != zipFile)
        {
            IoUtil.close(zipFile);
            zipFile = null;
        }
        fileMap.clear();
        Set(null, null, null, null);
    }

    public boolean Test(String[] res)
    {
        StringBuilder sb = new StringBuilder();
        boolean r = true;
        List<String> files;
        Map<String, String> map = new LinkedHashMap<>();

        Function<String, String> f = (x) -> {
            String str = GetExtractPath(x);
            map.put(x, str);
            return x + " -> " + str;
        };

        files = ListFileInZip("main/java/com/ruoyi/.*/domain/.*\\.java");
        r = r && CollectionUtil.isNotEmpty(files);
        sb.append("domain.java:\n")
          .append("\t");
        if(CollectionUtil.isNotEmpty(files))
            sb.append(files.stream().map(f).collect(Collectors.joining("\n\t")));
        else
            sb.append("缺失!");
        sb.append("\n\n");

        files = ListFileInZip("main/java/com/ruoyi/.*/service/.*\\.java");
        r = r && CollectionUtil.isNotEmpty(files);
        sb.append("service.java:\n")
          .append("\t");
        if(CollectionUtil.isNotEmpty(files))
            sb.append(files.stream().map(f).collect(Collectors.joining("\n\t")));
        else
            sb.append("缺失!");
        sb.append("\n\n");

//        files = ListFileInZip("main/java/com/ruoyi/.*/service/impl/.*\\.java");
//        r = r && CollectionUtil.isNotEmpty(files);
//        sb.append("service_impl.java:\n")
//          .append("\t")
//          .append(files.stream().map(f).collect(Collectors.joining("\n\t")))
//        ;
//        sb.append("\n\n");

        files = ListFileInZip("main/java/com/ruoyi/.*/mapper/.*\\.java");
        r = r && CollectionUtil.isNotEmpty(files);
        sb.append("mapper.java:\n")
          .append("\t");
        if(CollectionUtil.isNotEmpty(files))
            sb.append(files.stream().map(f).collect(Collectors.joining("\n\t")));
        else
            sb.append("缺失!");
        sb.append("\n\n");

        files = ListFileInZip("main/java/com/ruoyi/.*/controller/.*\\.java");
        r = r && CollectionUtil.isNotEmpty(files);
        sb.append("controller.java:\n")
          .append("\t");
        if(CollectionUtil.isNotEmpty(files))
            sb.append(files.stream().map(f).collect(Collectors.joining("\n\t")));
        else
            sb.append("缺失!");
        sb.append("\n\n");

        files = ListFileInZip("main/resources/mapper/.*/.*\\.xml");
        r = r && CollectionUtil.isNotEmpty(files);
        sb.append("MyBatis.xml:\n")
          .append("\t");
        if(CollectionUtil.isNotEmpty(files))
            sb.append(files.stream().map(f).collect(Collectors.joining("\n\t")));
        else
            sb.append("缺失!");
        sb.append("\n\n");

        files = ListFileInZip("vue/.*\\.vue");
        r = r && CollectionUtil.isNotEmpty(files);
        sb.append("Vue:\n")
          .append("\t");
        if(CollectionUtil.isNotEmpty(files))
            sb.append(files.stream().map(f).collect(Collectors.joining("\n\t")));
        else
            sb.append("缺失!");
        sb.append("\n\n");

        files = ListFileInZip("vue/.*\\.js");
        r = r && CollectionUtil.isNotEmpty(files);
        sb.append("JavaScript:\n")
          .append("\t");
        if(CollectionUtil.isNotEmpty(files))
            sb.append(files.stream().map(f).collect(Collectors.joining("\n\t")));
        else
            sb.append("缺失!");
        sb.append("\n");

        if(null != res && res.length > 0)
            res[0] = sb.toString();
        fileMap.clear();
        if(r)
            fileMap.putAll(map);
        return r;
    }

    public boolean IsValid()
    {
        return StringUtils.isNotEmpty(zipPath)
                && StringUtils.isNotEmpty(javaProjectPath)
                && StringUtils.isNotEmpty(webProjectPath)
                ;
    }

    public boolean Begin()
    {
        try
        {
            zipFile = new ZipFile(zipPath);
            return true;
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return false;
        }
    }

    public boolean Extract(BiFunction<String, String, Integer> ifExists)
    {
        if(CollectionUtil.isEmpty(fileMap))
            return false;
        for(Map.Entry<String, String> entry : fileMap.entrySet())
        {
            String src = entry.getKey();
            String dst = entry.getValue();
            File d = new File(dst);
            if(d.exists())
            {
                if(null != ifExists)
                {
                     int ife = ifExists.apply(src, dst);
                     if(ife == 0)
                         continue;
                     if(ife < 0)
                         return false;
                }
            }
            if(!Copy(src, dst))
                return false;
        }
        return true;
    }

    private boolean Copy(String src, String dst)
    {
        File d = new File(dst);
        d.deleteOnExit();
        if(mkdirs(d.getParent()) < 0)
            return false;
        byte[] source = ReadFileInZip(src);
        if(null == source)
            return false;
        return null != FileUtil.writeBytes(source, d);
    }

    public void End()
    {
    }

    private String GuessModuleName(String zipPath)
    {
        if(IsJava(zipPath))
        {
            int i = "main/java/com/ruoyi/".length();
            int e = zipPath.indexOf("/", i);
            return zipPath.substring(i, e);
        }
        else if(IsMybatis(zipPath))
        {
            int i = "main/resources/mapper/".length();
            int e = zipPath.indexOf("/", i);
            return zipPath.substring(i, e);
        }
        return null;
    }

    private String GetModuleName(String zipPath)
    {
        if(StringUtils.isNotEmpty(module))
            return module;
        return GuessModuleName(zipPath);
    }

    private String GetExtractPath(String zipPath)
    {
        File file = new File(zipPath);
        String fileName = file.getName();
        if(IsJava(zipPath))
        {
            if(IsController(zipPath))
            {
                String name = GuessModuleName(zipPath);
                return javaProjectPath + "/ruoyi-admin/src/main/java/com/ruoyi/web/controller/" + name + "/" + fileName;
            }
            else
            {
                String moduleName = GetModuleName(zipPath);
                return javaProjectPath + "/ruoyi-" + moduleName + "/src/" + zipPath;
            }
        }
        else if(IsMybatis(zipPath))
        {
            String moduleName = GetModuleName(zipPath);
            return javaProjectPath + "/ruoyi-" + moduleName + "/src/" + zipPath;
        }
        else if(IsVue(zipPath) || IsJavaScript(zipPath))
        {
            return webProjectPath + "/ruoyi-ui/src" + zipPath.substring("vue".length());
        }
        return null;
    }

    private boolean IsJava(String zipPath)
    {
        return zipPath.endsWith(".java");
    }

    private boolean IsController(String zipPath)
    {
        return zipPath.endsWith("Controller.java");
    }

    private boolean IsMybatis(String zipPath)
    {
        return zipPath.endsWith(".xml");
    }

    private boolean IsVue(String zipPath)
    {
        return zipPath.endsWith(".vue");
    }

    private boolean IsJavaScript(String zipPath)
    {
        return zipPath.endsWith(".js");
    }

    private List<String> ListFileInZip(String regexp)
    {
        return zipFile.stream()
                      .map(ZipEntry::getName)
                      .filter((x) -> {
                          return (Pattern.matches(regexp, x));
                      }).collect(Collectors.toList());
    }

    private byte[] ReadFileInZip(String path)
    {
        ZipEntry entry = zipFile.getEntry(path);
        if(null == entry)
            return null;

        InputStream is = null;

        try
        {
            is = zipFile.getInputStream(entry);
            return IoUtil.readBytes(is);
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return null;
        }
        finally
        {
            IoUtil.close(is);
        }
    }

    private int mkdirs(String path)
    {
        File file = new File(path);
        if(file.exists())
        {
            if(file.isDirectory())
                return 0;
            else
                return -1;
        }
        return file.mkdirs() ? 1 : -2;
    }

    private String AppendPath(String basePath, String filePath)
    {
        String path = basePath + File.separator + filePath;
        File file = new File(path);
        try
        {
            return file.getCanonicalPath();
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return path;
        }
    }

    public static void main(String[] args)
    {
        ExtractGeneratedSourceGUI program = new ExtractGeneratedSourceGUI();
        program.setVisible(true);
    }
}
