package com.zjty.inspect.service.impl;

import com.zjty.inspect.dao.ReportDao;
import com.zjty.inspect.entity.*;
import com.zjty.inspect.enums.LanguageEnum;
import com.zjty.inspect.service.InspectService;
import com.zjty.inspect.inspect.Inspector;
import com.zjty.inspect.service.ParameterService;
import com.zjty.inspect.service.TechnologyService;
import com.zjty.inspect.utils.FileUtil;
import com.zjty.inspect.utils.FreemarkerUtils;
import com.zjty.inspect.utils.TimeUtil;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import java.io.*;
import java.io.File;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * 解析
 * @author mcj
 */
@Service
public class InspectServiceImpl implements InspectService {

    @Autowired
    Inspector inspector;

    @Autowired
    private ReportDao reportDao;

    @Autowired
    private TechnologyService technologyService;
    @Autowired
    private ParameterService parameterService;

    @Override
    public File chunkUploadByMappedByteBuffer(MultipartFileParam param, String filePath) throws Exception{
        System.out.println("============================");
        if(param.getTaskId() == null || "".equals(param.getTaskId())){
            param.setTaskId(UUID.randomUUID().toString());
        }
        /**
         *
         * 1：创建临时文件，和源文件一个路径
         * 2：如果文件路径不存在重新创建
         */
        String fileName = param.getFile().getOriginalFilename();
        String tempFileName = param.getTaskId() + fileName.substring(fileName.lastIndexOf(".")) + "_tmp";
        java.io.File fileDir = new java.io.File(filePath);

        if(!fileDir.exists()){
            fileDir.mkdirs();
        }
        java.io.File tempFile = new java.io.File(filePath,tempFileName);
        //第一步
        RandomAccessFile raf = new RandomAccessFile(tempFile,"rw");
        //第二步
        FileChannel fileChannel = raf.getChannel();
        //第三步 计算偏移量
        long position = (param.getChunkNumber()-1) * param.getChunkSize();
        //第四步
        byte[] fileData = param.getFile().getBytes();
        //第五步
        long end=position+fileData.length-1;
        fileChannel.position(position);
        fileChannel.write(ByteBuffer.wrap(fileData));
        //使用 fileChannel.map的方式速度更快，但是容易产生IO操作，无建议使用
//        MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE,position,fileData.length);
//        //第六步
//        mappedByteBuffer.put(fileData);
        //第七步
//        freedMappedByteBuffer(mappedByteBuffer);
//        Method method = FileChannelImpl.class.getDeclaredMethod("unmap", MappedByteBuffer.class);
//        method.setAccessible(true);
//        method.invoke(FileChannelImpl.class, mappedByteBuffer);
        fileChannel.force(true);
        fileChannel.close();
        raf.close();
        //第八步
        boolean isComplete = checkUploadStatus(param,fileName,filePath);
        if(isComplete){
            System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaa");
            renameFile(tempFile,fileName);
            java.io.File realFile = new java.io.File(filePath,fileName);
            FileItem fileItem = createFileItem(realFile, filePath);
            MultipartFile mfile = new CommonsMultipartFile(fileItem);
            return FileUtil.saveToLocal(mfile);
        }
        System.out.println("qqqqqqqqqqqqqqqqqqqq");
//        return param.getTaskId();
        return null;
    }

    /*
    创建FileItem
     */
    private FileItem createFileItem(File file, String fieldName) {
        FileItemFactory factory = new DiskFileItemFactory(16, null);
        FileItem item = factory.createItem(fieldName, "text/plain", true, file.getName());
        int bytesRead = 0;
        byte[] buffer = new byte[8192];
        try {
            FileInputStream fis = new FileInputStream(file);
            OutputStream os = item.getOutputStream();
            while ((bytesRead = fis.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            os.close();
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return item;
    }


    /**
     * 文件重命名
     * @param toBeRenamed   将要修改名字的文件
     * @param toFileNewName 新的名字
     * @return
     */
    public void renameFile(java.io.File toBeRenamed, String toFileNewName) {
        //检查要重命名的文件是否存在，是否是文件
        if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
            System.out.println("文件不存在");
            return;
        }
        String p = toBeRenamed.getParent();
        java.io.File newFile = new java.io.File(p + java.io.File.separatorChar + toFileNewName);
        //修改文件名
        toBeRenamed.renameTo(newFile);
    }

    /**
     * 检查文件上传进度
     * @return
     */
    public boolean checkUploadStatus(MultipartFileParam param,String fileName,String filePath) throws IOException {
        java.io.File confFile = new java.io.File(filePath,fileName+".conf");
        RandomAccessFile confAccessFile = new RandomAccessFile(confFile,"rw");
        //设置文件长度
        confAccessFile.setLength(param.getTotalChunks());
        //设置起始偏移量
        confAccessFile.seek(param.getChunkNumber()-1);
        //将指定的一个字节写入文件中 127，
        confAccessFile.write(Byte.MAX_VALUE);
        byte[] completeStatusList = FileUtils.readFileToByteArray(confFile);
        confAccessFile.close();//不关闭会造成无法占用
        //这一段逻辑有点复杂，看的时候思考了好久，创建conf文件文件长度为总分片数，每上传一个分块即向conf文件中写入一个127，那么没上传的位置就是默认的0,已上传的就是Byte.MAX_VALUE 127
        for(int i = 0; i<completeStatusList.length; i++){
            if(completeStatusList[i]!=Byte.MAX_VALUE){
                return false;
            }
        }
        //如果全部文件上传完成，删除conf文件
        confFile.delete();
        return true;
    }

    /**
     * 在MappedByteBuffer释放后再对它进行读操作的话就会引发jvm crash，在并发情况下很容易发生
     * 正在释放时另一个线程正开始读取，于是crash就发生了。所以为了系统稳定性释放前一般需要检 查是否还有线程在读或写
     * @param mappedByteBuffer
     */
    public static void freedMappedByteBuffer(final MappedByteBuffer mappedByteBuffer) {
        try {
            if (mappedByteBuffer == null) {
                return;
            }
            mappedByteBuffer.force();
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                @Override
                public Object run() {
                    try {
                        Method getCleanerMethod = mappedByteBuffer.getClass().getMethod("cleaner", new Class[0]);
                        //可以访问private的权限
                        getCleanerMethod.setAccessible(true);
                        //在具有指定参数的 方法对象上调用此 方法对象表示的底层方法
                        sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(mappedByteBuffer,
                                new Object[0]);
                        cleaner.clean();
                    } catch (Exception e) {
                        e.printStackTrace();
                        System.out.println("清理缓存出错!!!"+e.getMessage());
                    }
                    System.out.println("缓存清理完毕!!!");
                    return null;
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Transactional
    @Override
    public ReportVo inspect(ReportVo reportVo,InspectParameter inspectParameter) throws IOException {
        //统计文件后缀数量
        Map<String, LanguageEnum> suffixLanguageMapping = new HashMap<>();
        suffixLanguageMapping.put("java", LanguageEnum.JAVA);
        suffixLanguageMapping.put("aspx", LanguageEnum.C);
        suffixLanguageMapping.put("cs", LanguageEnum.C);
        suffixLanguageMapping.put("resx", LanguageEnum.C);
        suffixLanguageMapping.put("vue", LanguageEnum.JAVASCRIPT);
        suffixLanguageMapping.put("cpp", LanguageEnum.C);
        suffixLanguageMapping.put("py", LanguageEnum.PYTHON);
        suffixLanguageMapping.put("jsp", LanguageEnum.JSP);
        suffixLanguageMapping.put("go", LanguageEnum.GO);
        suffixLanguageMapping.put("js", LanguageEnum.JAVASCRIPT);
        suffixLanguageMapping.put("html", LanguageEnum.HTML);
        suffixLanguageMapping.put("php",LanguageEnum.PHP);
        suffixLanguageMapping.put("vbp",LanguageEnum.VB);
        suffixLanguageMapping.put("rs",LanguageEnum.RUST);
        suffixLanguageMapping.put("dpk",LanguageEnum.DELPHI);

        //构造成员变量
        inspector.setInspectParameter(inspectParameter);
        //构造报告vo
        inspector.setReport(reportVo);
        //需要重构
        inspector.setSuffixLanguageMapping(suffixLanguageMapping);
        ReportVo reportVoReturn = inspector.inspect();
        return reportVoReturn;
    }

    /**
     * 处理数据
     * @return
     * count 总共的关键技术
     * notSupport 不支持的关键技术数量
     */
    @Override
    public String freemakerData(ReportVo inspect, int count, int notSupport) {
        try {
        Map map = new HashMap();
        map.put("inspect", inspect);
        map.put("time", TimeUtil.getTime());
        HashMap<String, List<Warn>> warnMap = inspect.getWarnDetails();
        List<Technology> technologies = technologyService.findAllTechnology();
        Map techMap = new HashMap();
        for (Technology technology : technologies) {
            techMap.put(technology.getTechnologyName(), technology.getSupport());
        }
        map.put("techMap", techMap);
        map.put("count",count);
        map.put("notSupport",notSupport);
        map.put("warnMap", warnMap);
        map.put("technologies", technologies);

            String template = FreemarkerUtils.getTemplate("pg.ftl", map);
            String s = generateHtml(template, map);
            String filePath = FileUtil.createFilePath();
            FileUtil.write(s,"/opt/inspect/freemaker/"+filePath+"/"+inspect.getProjectName()+".html");
            String fn="/freemaker/"+filePath+"/"+inspect.getProjectName()+".html";
            return fn;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    //执行静态化
    @Override
    public String generateHtml(String templateContent,Map model ){

        //创建配置对象
        Configuration configuration = new Configuration(Configuration.getVersion());
        //创建模板加载器
        StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
        stringTemplateLoader.putTemplate("template",templateContent);
        //向configuration配置模板加载器
        configuration.setTemplateLoader(stringTemplateLoader);
        //获取模板
        try {
            Template template = configuration.getTemplate("template");
            //调用api进行静态化
            String content = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
            return content;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }


}
