package com.ruoyi;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.annotation.Info;
import lombok.Data;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.junit.jupiter.api.Test;
import org.springframework.data.annotation.Id;

import java.io.File;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Info
public class MongoGenerator {

  /**
   * 输出的类的根包，扫描的实体类为该包下的mongo子包下
   */
  private String packageName = "com.ruoyi.jobs";

  /**
   * 模块名，类的简单名称会先去掉前面的模块名
   */
  private String moduleName = "jobs";

  @Test
  public void generate() throws Exception {

    List<BaseInfo> entityList = scan(); //所有编写了@Info的实体类
//    List<BaseInfo> entityList = scan("JobsTag1");//编写了@Info的实体类中的某些类

    System.out.println(new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(entityList));
    generate(entityList);
    System.out.println("完成");
  }

  public void generate(List<BaseInfo> entityList) throws Exception {

    File[] fs = getOutPath();

    /**
     * 工程根
     */
    File root = fs[0];
    /**
     * java包根目录
     */
    File javaRoot = fs[1];

    for (BaseInfo entity : entityList) {


      String code = generate("query.vm", entity, entityList);
      new File(javaRoot, "mongo/query").mkdirs();
      FileUtil.writeString(code, new File(javaRoot, "mongo/query/" + entity.toUpperName() + "Query.java"), "UTF-8");

      code = generate("api.vm", entity, entityList);
      new File(javaRoot, "api").mkdirs();
      FileUtil.writeString(code, new File(javaRoot, "api/" + entity.toUpperName() + "Api.java"), "UTF-8");


      code = generate("api.js.vm", entity, entityList);
      new File(root, "admin-ui/src/views/" + moduleName + "/api").mkdirs();
      FileUtil.writeString(code, new File(root, "admin-ui/src/views/" + moduleName + "/api/" + entity.toSimpleLowerName() + ".js"), "UTF-8");

      code = generate("index.vue.vm", entity, entityList);
      new File(root, "admin-ui/src/views/" + moduleName + "/" + entity.toSimpleLowerName()).mkdirs();
      FileUtil.writeString(code, new File(root, "admin-ui/src/views/" + moduleName + "/" + entity.toSimpleLowerName() + "/index.vue"), "UTF-8");

      code = generate("WAdd.vue.vm", entity, entityList);
      new File(root, "admin-ui/src/views/" + moduleName + "/" + entity.toSimpleLowerName() + "/components").mkdirs();
      FileUtil.writeString(code, new File(root, "admin-ui/src/views/" + moduleName + "/" + entity.toSimpleLowerName() + "/components/WAdd.vue"), "UTF-8");

      code = generate("WEdit.vue.vm", entity, entityList);
      FileUtil.writeString(code, new File(root, "admin-ui/src/views/" + moduleName + "/" + entity.toSimpleLowerName() + "/components/WEdit.vue"), "UTF-8");


    }

    String code = generate("sql.vm", null, entityList);
    FileUtil.writeString(code, new File(root, moduleName + "-menu.sql"), "UTF-8");
  }

  /**
   * 使用模板引擎生成代码
   *
   * @param template
   * @param entity
   * @param list
   * @return
   * @throws Exception
   */
  public String generate(String template, BaseInfo entity, List<BaseInfo> list) throws Exception {
    VelocityEngine ve = new VelocityEngine();
    ve.init();

    VelocityContext context = new VelocityContext();
    context.put("package", packageName);
    context.put("entity", entity);
    context.put("list", list);
    context.put("moduleName", moduleName);
    StringWriter writer = new StringWriter();

    template = ResourceUtil.readUtf8Str("classpath:/generator/" + template);

    ve.evaluate(context, writer, this.getClass().getName(), template);

    return writer.toString();
  }

  /**
   * 获取输出的java包源目录
   *
   * @return
   */
  public File[] getOutPath() {
    Set<String> paths = ClassUtil.getClassPaths(packageName);
    if (CollUtil.isEmpty(paths) || paths.size() != 1) {
      throw new RuntimeException("不能确定java包源目录");
    }
    String path = paths.iterator().next();
    File file = new File(path.replace("/target/classes/", "/src/main/java/"));
    File root = new File(path.substring(0, path.indexOf("/target/classes/"))).getParentFile();


    return new File[]{root, file};
  }

  /**
   * 扫描获取包下需要生成的实体信息
   *
   * @param includes 包含的类名，不填表示扫描所有
   * @return
   */
  public List<BaseInfo> scan(String... includes) {
    Info defaultInfo = MongoGenerator.class.getAnnotation(Info.class);
    List<BaseInfo> entityList = ListUtil.list(true);

    ClassUtil.scanPackageByAnnotation(packageName + ".mongo", Info.class).stream().filter(c -> {
      if (ArrayUtil.isEmpty(includes)) {
        return true;
      } else {
        return ArrayUtil.indexOf(includes, c.getSimpleName()) > -1;
      }
    }).forEach(entityClazz -> {
      BaseInfo entityInfo = loadByInfo(entityClazz.getAnnotation(Info.class));
      entityList.add(entityInfo);
      entityInfo.setClazz(entityClazz);
      entityInfo.setJavaName(entityClazz.getSimpleName());
      entityInfo.setModuleName(moduleName);

      entityInfo.all = Arrays.stream(entityClazz.getDeclaredFields()).map(field -> {
        BaseInfo info = null;
        if (field.isAnnotationPresent(Info.class)) {
          info = loadByInfo(field.getAnnotation(Info.class));
        } else {
          info = loadByInfo(defaultInfo);
        }
        info.setClazz(field.getType());
        info.setJavaName(field.getName());
        info.setIsId(field.isAnnotationPresent(Id.class));
        info.setModuleName(moduleName);
        return info;
      }).sorted((a, b) -> a.order - b.order).collect(Collectors.toList());
      entityInfo.cols = entityInfo.all.stream().filter(a -> !a.isId).collect(Collectors.toList());
      entityInfo.id = entityInfo.all.stream().filter(a -> a.isId).findFirst().get();
    });
    return entityList;
  }

  public BaseInfo loadByInfo(Info info) {
    BaseInfo base = new BaseInfo();
    base.setAdd(info.add());
    base.setUpdate(info.update());
    base.setShow(info.show());
    base.setOrder(info.order());
    base.setName(info.value());
    if (!info.addDefault().equals("")) {
      base.setAddDefault(info.addDefault());
    }
    if (!info.updateDefault().equals("")) {
      base.setUpdateDefault(info.updateDefault());
    }
    if (!info.comment().equals("")) {
      base.setComment(info.comment());
    }

    return base;
  }


  @Data
  public static class BaseInfo {

    public Class<?> clazz;

    public Field field;

    /**
     * 所有字段
     */
    public List<BaseInfo> all;

    /**
     * 不含主键
     */
    public List<BaseInfo> cols;

    /**
     * 主键字段
     */
    public BaseInfo id;

    public Boolean isId = false;

    public String name;

    public String getName() {
      if (StrUtil.isBlank(name)) {
        return javaName;
      } else {
        return name;
      }
    }

    public String javaName;

    public String moduleName;

    public String toUpperName() {
      return javaName.substring(0, 1).toUpperCase() + javaName.substring(1);
    }

    /**
     * 去掉模块名后的
     *
     * @return
     */
    public String toSimpleUpperName() {
      String n = toLowerName();
      if (n.startsWith(moduleName)) {
        return n.substring(moduleName.length());
      } else {
        return toUpperName();
      }
    }


    public String toSimpleLowerName() {
      String n = toLowerName();
      if (n.startsWith(moduleName)) {
        n = n.substring(moduleName.length());
        return n.substring(0, 1).toLowerCase() + n.substring(1);
      } else {
        return toLowerName();
      }
    }

    public String toLowerName() {
      return javaName.substring(0, 1).toLowerCase() + javaName.substring(1);
    }

    public String comment;

    /**
     * @param tabNum
     * @return
     */
    public String toComment(int tabNum) {
      if (tabNum < 0) {
        tabNum = 0;
      }
      String tabs = tabNum > 0 ? String.format("%" + (tabNum * 2) + "s", "") + " * " : " * ";
      if (StrUtil.isNotBlank(comment)) {
        return tabs + comment.replace("\n", "\n" + tabs);
      } else {
        return tabs;
      }
    }


    public boolean show;

    public boolean add;
    public String addDefault;

    public boolean update;
    public String updateDefault;

    public int order;


  }
}
