package org.bdware.sc;

import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bdware.sc.encrypt.HardwareInfo;
import org.bdware.sc.encrypt.HardwareInfo.OSType;
import org.bdware.sc.gen.PYGenerator;
import org.bdware.sc.node.ContractManifest;
import org.bdware.sc.packer.AvatarHelper;
import org.bdware.sc.py.PYEntry;
import org.bdware.sc.py.bean.PYModule;
import org.bdware.sc.py.bean.PYPackage;
import org.bdware.sc.util.JsonUtil;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

public class YJSPacker {
    private static final String MANIFEST_NAME = "manifest.json";
    private static final String JAR_NAME = "py.jar";
    private static final Logger LOGGER = LogManager.getLogger(YJSPacker.class);
    private static final String LOGO_NAME = "logo.png";

    public static byte[] pack(String dir) {
        try {
            File d = new File(dir);
            ByteArrayOutputStream fout = new ByteArrayOutputStream();
            ZipOutputStream zout = new ZipOutputStream(fout);
            updateManifest(dir + "/" + MANIFEST_NAME);
            generateLogoIfNotExist(dir);
            walkDir(d.getAbsolutePath(), d, zout);
            ByteArrayOutputStream bo = new ByteArrayOutputStream();
            boolean hasContent =
                    packFromManifest(new ZipOutputStream(bo), dir + "/" + MANIFEST_NAME);
            if (hasContent) {
                zout.putNextEntry(new ZipEntry(JAR_NAME));
                zout.write(bo.toByteArray());
                zout.closeEntry();
            }
            zout.close();
            fout.close();
            return fout.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void pack(String dir, String target) {
        try {
            File d = new File(dir);
            File parent = new File(target).getParentFile();
            if (!parent.exists()) {
                LOGGER.trace("create directory " + parent.getAbsolutePath() + ": " + parent.mkdirs());
            }
            FileOutputStream fout = new FileOutputStream(target);
            ZipOutputStream zout = new ZipOutputStream(fout);
            //System.out.println("[YJSPaker]"+dir+MANIFEST_NAME);
            updateManifest(dir + "/" + MANIFEST_NAME);
            generateLogoIfNotExist(dir);
            walkDir(d.getAbsolutePath(), d, zout);
            ByteArrayOutputStream bo = new ByteArrayOutputStream();
            boolean hasContent =
                    packFromManifest(new ZipOutputStream(bo), dir + "/" + MANIFEST_NAME);
            if (hasContent) {
                zout.putNextEntry(new ZipEntry(JAR_NAME));
                zout.write(bo.toByteArray());
                zout.closeEntry();
            }
            zout.close();
            fout.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void generateLogoIfNotExist(String dir) {
        File logoDir = new File(dir, "assets");
        if (!logoDir.exists()) {
            LOGGER.trace("create directory " + logoDir.getAbsolutePath() + ": " + logoDir.mkdirs());
        }
        File logo = new File(logoDir, LOGO_NAME);
        String builderName = new File(dir).getParentFile().getName();
        String sourcePath = builderName + "/" + new File(dir).getName();
        if (!logo.exists()) {
            try {
                FileUtils.writeByteArrayToFile(logo, AvatarHelper.create(sourcePath));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static void updateManifest(String s) throws Exception {
        File manifest = new File(s);
        String content = FileUtils.readFileToString(manifest, "UTF-8");
        LOGGER.debug(content);
        ContractManifest cm = JsonUtil.fromJson(content, ContractManifest.class);
        String builderName = manifest.getParentFile().getParentFile().getName();
        cm.sourcePath = builderName + "/" + manifest.getParentFile().getName();
        LOGGER.info("DOI:"+cm.doi);
        if (null == cm.doi || cm.doi.isEmpty()) {
            cm.doi = "Contract_" + cm.sourcePath.hashCode();
        }
        cm.buildTime = System.currentTimeMillis();
        if (builderName.length() > 100 && builderName.startsWith("04")) {
            cm.builder = builderName;
        } else {
            cm.builder = "ContractEngine";
        }
        FileUtils.writeStringToFile(manifest, JsonUtil.toPrettyJson(cm), "UTF-8");
    }

    private static void walkDir(String preFix, File d, ZipOutputStream zout) {
        try {
            if (d.isDirectory()) {
                for (File f : d.listFiles()) {
                    walkDir(preFix, f, zout);
                }
            } else {
                String entryName = d.getAbsolutePath().replace(preFix, "");
                if (HardwareInfo.type == OSType.win) {
                    entryName = entryName.replaceAll("\\\\", "/");
                }
                zout.putNextEntry(new ZipEntry(entryName));
                FileInputStream fin = new FileInputStream(d);
                byte[] buff = new byte[1024];
                for (int l; (l = fin.read(buff)) > 0; ) {
                    zout.write(buff, 0, l);
                }
                zout.closeEntry();
                fin.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static boolean packFromManifest(ZipOutputStream zout, String manifestPath) {
        boolean hasContent = false;
        try {
            // 解析manifest.json文件
            LOGGER.debug("manifestPath = " + manifestPath);
            File file = new File(manifestPath);
            String content = FileUtils.readFileToString(file, "UTF-8");
            LOGGER.debug(content);
            ContractManifest cm = JsonUtil.fromJson(content, ContractManifest.class);
            if (cm.getInsnLimit() != 0) {
                LOGGER.debug(cm.insnLimit);
            }
            // System.out.println(cm.packages.get(1).getModules().get(0).getName());
            // 调用接口获得PYPackage对象
            if (cm.getPyDependences() != null)
                for (PYPackage pyPkg : cm.getPyDependences()) {
                    List<PYModule> list = new ArrayList<>();
                    if (pyPkg.getModules() == null || pyPkg.getModules().isEmpty()) {
                        pyPkg = PYEntry.instance.packageInfo(pyPkg.getName());
                    } else {
                        for (PYModule pyModule : pyPkg.getModules()) {
                            PYModule info =
                                    PYEntry.instance.moduleInfo(
                                            pyPkg.getName() + "." + pyModule.getName());
                            info.setName(pyModule.getName());
                            LOGGER.debug(pyPkg.getName() + "." + pyModule.getName());
                            list.add(info);
                        }
                    }
                    if (!list.isEmpty()) {
                        pyPkg.setModules(list);
                    }
                    // 调用GenP$M$C生成classfiles
                    PYGenerator pmc = new PYGenerator();
                    hasContent = true;
                    pmc.genClassFiles(zout, pyPkg);
                }
            zout.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return hasContent;
    }

    public static <T> T getConfig(String input, String path, Class<T> clz) {
        ZipFile zin = null;
        try {
            zin = new ZipFile(new File(input));
            ZipEntry entry = zin.getEntry(path);
            return JsonUtil.fromJson(new InputStreamReader(zin.getInputStream(entry)), clz);
        } catch (Exception e) {
            LOGGER.warn("read " + clz.getSimpleName() + " failed: " + e.getMessage());
        } finally {
            if (null != zin) {
                try {
                    zin.close();
                } catch (Exception e) {
                    LOGGER.error("closing zin failed: " + e.getMessage());
                }
            }
        }
        return null;
    }

    public static ContractManifest getMan2(String input, String path) {
        ZipFile zin = null;
        try {
            zin = new ZipFile(new File(input));
            ZipEntry entry = zin.getEntry(path);
            return JsonUtil.fromJson(new InputStreamReader(zin.getInputStream(entry)), ContractManifest.class);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            if (zin != null) {
                try {
                    zin.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    public static ContractManifest getManifest(String input) {
        try {
            ZipInputStream zin = new ZipInputStream(new FileInputStream(input));
            byte[] buff = new byte[1024 * 100];
            InputStream manifestInput = null;
            for (ZipEntry entry; (entry = zin.getNextEntry()) != null; ) {
                if (entry.getName().endsWith("manifest.json")) {
                    for (int k; (k = zin.read(buff)) > 0; ) {
                        manifestInput = new ByteArrayInputStream(buff, 0, k);
                    }
                }
                zin.closeEntry();
            }
            zin.close();
            if (manifestInput != null) {
                return JsonUtil.fromJson(
                        new InputStreamReader(manifestInput), ContractManifest.class);
            } else {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void unpack(String input, String dir) {
        try {
            unpackInternal(new FileInputStream(input), dir);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static void unpack(byte[] input, String dir) {
        unpackInternal(new ByteArrayInputStream(input), dir);
    }

    private static void unpackInternal(InputStream input, String dir) {
        try {
            ZipInputStream zin = new ZipInputStream(input);
            byte[] buff = new byte[1024 * 100];
            for (ZipEntry entry; (entry = zin.getNextEntry()) != null; ) {
                File target = new File(dir, entry.getName());
                File parent = target.getParentFile();
                if (!parent.exists()) {
                    LOGGER.trace("create directory " + parent.getAbsolutePath() + ": " + parent.mkdirs());
                }
                if (!target.exists()) {
                    if (entry.isDirectory()) {
                        target.mkdirs();
                    } else {
                        target.createNewFile();
                    }
                }
                if (!entry.isDirectory()) {
                    FileOutputStream fout = new FileOutputStream(target);
                    for (int k; (k = zin.read(buff)) > 0; ) {
                        fout.write(buff, 0, k);
                    }
                    fout.close();
                    zin.closeEntry();
                }
            }
            zin.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
