package com.jeeplus.common.oss;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.*;
import com.jeeplus.common.utils.StringUtils;
import com.jeeplus.modules.workpartetag.entity.WorkPartEtag;
import com.jeeplus.modules.workpartetag.service.WorkPartEtagService;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import java.io.*;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @author
 * @create 2016-10-31 19:13
 * @others 阿里云OSS测试
 */
@Controller
@RequestMapping(value = "${adminPath}/sys/upload")
public class MultipartUpload extends OSSConfig{

    //日志记录
    private static final Logger LOG = LoggerFactory.getLogger(MultipartUpload.class);

    //获取配置文件
    //ResourceBundle resourceBundle = ResourceBundle.getBundle("config");

    //默认5个线程
    private static ExecutorService executorService = Executors.newFixedThreadPool(5);

    //存储OSS返回来的partETags，是一个线程同步的arraylist，靠这个去查询
    private static List<PartETag> partETags = Collections.synchronizedList(new ArrayList<PartETag>());

    //如果上传完毕，uploadId则失效，这里将client设为null，则进度查询会显示完毕（不是必须）
    private static OSSClient client = null;

    // 创建一个要存放的Object的名称，使用文件名怕重复，直接改名+存入数据库
    private static String key;

    //默认一个静态的字段贮备，查询进度需要
    private static String uploadId;

    //默认一个静态的字段储备文件分割的数量，进度查询需要
    private static Integer partCount;

    //标识符，false情况下说名还没有开始，true则前端可以开始查询进度（不是必须）
    private boolean progress = false;

    // 每片最少5MB，这里写死了，最好用配置文件随时修改
    final long partSize = 5 * 1024 * 1024L;

    @Autowired
    private WorkPartEtagService workPartEtagService;

    @ResponseBody
    @RequestMapping("upload")
    public void upload(@RequestParam(value = "file", required = false) MultipartFile mFile)
            throws IOException {
        //随便写一个文件名，这个是要和数据库关联的
        key = "app-data/appfiles/932059357.war";

        // MultipartFile转File，springmvc得到MultipartFile非常简单
        CommonsMultipartFile cf= (CommonsMultipartFile)mFile;
        DiskFileItem dFile = (DiskFileItem)cf.getFileItem();
        File file = dFile.getStoreLocation();

        // 构造OSSClient
        client = new OSSClient(endpoint, accessKeyId, accessKeySecret);

        try {
            // 1-获得uploadId
            uploadId = getUploadId(client, bucketName, key);

            // 2-断点续传分片
            partCount = getPartCount(file);

            // 3-开始配置子线程，开始上传多片文件到自己的bucket（oss的文件夹）中
            for(int i = 0; i < partCount; i++){
                WorkPartEtag workPartEtag = new WorkPartEtag();
                workPartEtag.setBucketName(bucketName);
                workPartEtag.setKey(key);
                workPartEtag.setPartNumber((i + 1)+"");
                workPartEtag.setUploadId(uploadId);
                workPartEtag.setSize(this.partSize+"");
                workPartEtag.setStatus("0");
                workPartEtagService.save(workPartEtag);
            }
            for (int i = 0; i < partCount; i++) {
                long startPos = i * partSize;
                long curPartSize = (i + 1 == partCount) ? (file.length() - startPos) : partSize;
                try{
                    executorService.execute(new PartUploader(file, startPos, curPartSize, i + 1, uploadId));
                }catch (Exception e){
                    executorService.shutdown();
                }
            }

            // 4-方法执行中（只是步骤展示，这里没有代码）

            // 5-将标识符设为true，前端开始可以访问到上传进度
            progress = true;

            // 6-等待所有的线程上传完毕后关闭线程
            executorService.shutdown();
            while (!executorService.isTerminated()) {
                try {
                    //如果有线程没有完毕，等待5秒
                    executorService.awaitTermination(5, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // 7-判断是否所有的分片都上传完毕（不是必须）
            judgePartCount();

            // 8-必须：在将所有数据Part都上传完成后对整个文件的分片验证。
            verification();

            // 9-成功，恢复初始值（恢复初始值是为了不让这次上传影响其他的文件上传）
            restoreDefaultValues();

            // 10-下载上传的文件（不是必须）
            //client.getObject(new GetObjectRequest(bucketName, key), new File(localFilePath));

        } catch (OSSException oe) {
            System.out.println("OSS错误原因：");
            System.out.println("Error Message: " + oe.getErrorCode());
            System.out.println("Error Code:       " + oe.getErrorCode());
            System.out.println("Request ID:      " + oe.getRequestId());
            System.out.println("Host ID:           " + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("OSSClient错误原因：");
            System.out.println("Error Message: " + ce.getMessage());
        } finally {
            //注意关闭资源
            if (client != null) {
                client.shutdown();
            }
        }
    }

    @ResponseBody
    @RequestMapping("listParts")
    public void listParts()
            throws IOException {
        String key = "app-data/appfiles/932059357.war";
        String url = "F:\\code\\jeeplus_maven\\target\\jeeplus.war";
        WorkPartEtag workPartEtag = new WorkPartEtag();
        workPartEtag.setUploadId("B985174C128442268744D5D226A85324");
        workPartEtag.setPartNumber(27+"");
        WorkPartEtag workPartEtag1 = workPartEtagService.getByuploadId(workPartEtag);
        List<PartETag> partETags = partListing(key);
        multipartUploadListing();
        System.out.println("--------------233");
        return;
    }

    @ResponseBody
    @RequestMapping("uploadFile")
    public void uploadFile()
            throws Throwable {
        try{
            partETags = Collections.synchronizedList(new ArrayList<PartETag>());
            // 构造OSSClient
            client = new OSSClient(endpoint, accessKeyId, accessKeySecret);
            OSSClientUtil ossClientUtil = new OSSClientUtil();
            key = "app-data/appfiles/932059357.war";
            uploadId = "5C10BC9C728A41E69994DF0556B933E7";
            String url = "F:\\code\\jeeplus_maven\\target\\jeeplus.war";
            WorkPartEtag workPart = new WorkPartEtag();
            workPart.setUploadId(uploadId);
            workPart.setStatus("0");
            List<WorkPartEtag> workPartEtags= workPartEtagService.findList(workPart);
            workPart.setStatus("1");
            List<WorkPartEtag> workPartEtagolds= workPartEtagService.findList(workPart);
            for (WorkPartEtag workPartEtag:workPartEtagolds){
                PartETag partETag = new PartETag(Integer.parseInt(workPartEtag.getPartNumber()),workPartEtag.getETag(),Long.parseLong(workPartEtag.getSize()),Long.parseLong(workPartEtag.getPartCRC()));
                partETags.add(partETag);
            }
            List<String> numbers = new ArrayList<String>();
            for (WorkPartEtag workPartEtag:workPartEtags){
                String number = workPartEtag.getPartNumber();
                numbers.add(number);
            }
            File file = new File(url);
            // 2-断点续传分片
            partCount = getPartCount(file);

            // 3-开始配置子线程，开始上传多片文件到自己的bucket（oss的文件夹）中
            for (String id :numbers){
                int i = Integer.parseInt(id)-1;
                long startPos = i * partSize;
                long curPartSize = (i + 1 == partCount) ? (file.length() - startPos) : partSize;
                try {
                    executorService.execute(new PartUploader(file, startPos, curPartSize, i+1, uploadId));
                } catch (Exception e) {
                    executorService.shutdown();
                }
            }
            for (int i = 0; i < partCount; i++) {
                long startPos = i * partSize;
                long curPartSize = (i + 1 == partCount) ? (file.length() - startPos) : partSize;
                try {
                    executorService.execute(new PartUploader(file, startPos, curPartSize, i + 1, uploadId));
                } catch (Exception e) {
                    executorService.shutdown();
                }
            }

            // 4-方法执行中（只是步骤展示，这里没有代码）

            // 5-将标识符设为true，前端开始可以访问到上传进度
            progress = true;

            // 6-等待所有的线程上传完毕后关闭线程
            executorService.shutdown();
            while (!executorService.isTerminated()) {
                try {
                    //如果有线程没有完毕，等待5秒
                    executorService.awaitTermination(5, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // 7-判断是否所有的分片都上传完毕（不是必须）
            judgePartCount();

            // 8-必须：在将所有数据Part都上传完成后对整个文件的分片验证。
            verification();

            // 9-成功，恢复初始值（恢复初始值是为了不让这次上传影响其他的文件上传）
            restoreDefaultValues();
        } catch (OSSException oe) {
            System.out.println("OSS错误原因：");
            System.out.println("Error Message: " + oe.getErrorCode());
            System.out.println("Error Code:       " + oe.getErrorCode());
            System.out.println("Request ID:      " + oe.getRequestId());
            System.out.println("Host ID:           " + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("OSSClient错误原因：");
            System.out.println("Error Message: " + ce.getMessage());
        } finally {
            //注意关闭资源
            if (client != null) {
                client.shutdown();
            }
        }

    }

    /**
     * 获得uploadId
     */
    public String getUploadId(OSSClient client, String bucketName, String key){
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, key);
        InitiateMultipartUploadResult result = client.initiateMultipartUpload(request);
        return result.getUploadId();
    }

    /**
     * 上传分片
     */
    public void uploadSetEnableCheckpoint(String key,String localFile, List<PartETag> partETagList){
        List<PartETag> partETags = new ArrayList<PartETag>();
        InputStream instream = null;
        try {
            instream = new FileInputStream(new File(localFile));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        UploadPartRequest uploadPartRequest = new UploadPartRequest();
        uploadPartRequest.setBucketName(bucketName);
        uploadPartRequest.setKey(key);
        String uploadId = "490C8D60323F4AE3ACA2C627A785902A";
        uploadPartRequest.setUploadId(uploadId);
        uploadPartRequest.setInputStream(instream);
        // 设置分片大小，除最后一个分片外，其它分片要大于100KB
        uploadPartRequest.setPartSize(partSize);
        // 设置分片号，范围是1~10000，
        uploadPartRequest.setPartNumber(1);
        UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
        partETags.add(uploadPartResult.getPartETag());
        partETags.remove(partETagList);
        completeMultipartUpload(key,partETags);
    }

    /**
     * 完成分片上传
     */
    public void completeMultipartUpload(String key,List<PartETag> partETags){
        String uploadId = "490C8D60323F4AE3ACA2C627A785902A";
        Collections.sort(partETags, new Comparator<PartETag>() {
            @Override
            public int compare(PartETag p1, PartETag p2) {
                return p1.getPartNumber() - p2.getPartNumber();
            }
        });
        CompleteMultipartUploadRequest completeMultipartUploadRequest =
                new CompleteMultipartUploadRequest(bucketName, key, uploadId, partETags);
        ossClient.completeMultipartUpload(completeMultipartUploadRequest);
    }

    /**
     * 取消分片上传事件
     */
    public void abortMultipartUpload(String key){
        // 取消分片上传，其中uploadId来自于initiateMultipartUpload
        String uploadId = "490C8D60323F4AE3ACA2C627A785902A";
        AbortMultipartUploadRequest abortMultipartUploadRequest =
                new AbortMultipartUploadRequest(bucketName, key, uploadId);
        ossClient.abortMultipartUpload(abortMultipartUploadRequest);
    }

    /**
     * 简单列举分片上传事件
     */
    public void multipartUploadListing(){
        // 取消分片上传，其中uploadId来自于initiateMultipartUpload
        String uploadId = "490C8D60323F4AE3ACA2C627A785902A";
        // 列举分片上传事件
        ListMultipartUploadsRequest listMultipartUploadsRequest = new ListMultipartUploadsRequest(bucketName);
        MultipartUploadListing multipartUploadListing = ossClient.listMultipartUploads(listMultipartUploadsRequest);
        for (com.aliyun.oss.model.MultipartUpload multipartUpload : multipartUploadListing.getMultipartUploads()) {
            // Upload Id
            multipartUpload.getUploadId();
            // Key
            multipartUpload.getKey();
            // Date of initiate multipart upload
            multipartUpload.getInitiated();
        }
    }

    /**
     * 获取已上传的分片
     */
    public List<PartETag> partListing(String key){
        String uploadId = "490C8D60323F4AE3ACA2C627A785902A";
        // 列举已上传的分片，其中uploadId来自于initiateMultipartUpload
        ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, key, uploadId);
        PartListing partListing = ossClient.listParts(listPartsRequest);
        List<PartETag> partETags = new ArrayList<PartETag>();
        for (PartSummary part : partListing.getParts()) {
            PartETag partETag = new PartETag(part.getPartNumber(),part.getETag());
            // 分片号，上传时候指定
            part.getPartNumber();
            // 分片数据大小
            part.getSize();
            // Part的ETag
            partETags.add(partETag);
            // Part的最后修改上传
            part.getLastModified();
        }
        return partETags;
    }

    /**
     * 获取所有已上传分片
     */
    public ListPartsRequest listParts(String key){
        String uploadId = "490C8D60323F4AE3ACA2C627A785902A";;
        // 列举所有已上传的分片
        PartListing partListing;
        ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, key, uploadId);
        do {
            partListing = ossClient.listParts(listPartsRequest);
            for (PartSummary part : partListing.getParts()) {
                // 分片号，上传时候指定
                part.getPartNumber();
                // 分片数据大小
                part.getSize();
                // Part的ETag
                part.getETag();
                // Part的最后修改上传
                part.getLastModified();
            }
            listPartsRequest.setPartNumberMarker(partListing.getNextPartNumberMarker());
        } while (partListing.isTruncated());
        return listPartsRequest;
    }


    /**
     * 断点续传分片数量判断
     */
    public int getPartCount(File file){
        long fileLength = file.length();
        int partCount = (int) (fileLength / partSize); // 分片个数，不能超过10000
        if (fileLength % partSize != 0) {
            partCount++;
        }
        if (partCount > 10000) {
            throw new RuntimeException("分片不能超过10000，请修改每片大小");
        } else {
            return partCount;
        }
    }

    /**
     * 子线程上传（多线程的上传效果提升明显，如果单线程则在for循环中一个个上传即可）
     */
    private class PartUploader implements Runnable {
        private File localFile;
        private long startPos;
        private long partSize;
        private int partNumber;
        private String uploadId;
        public PartUploader(File localFile, long startPos, long partSize, int partNumber, String uploadId) {
            this.localFile = localFile;
            this.startPos = startPos;
            this.partSize = partSize;
            this.partNumber = partNumber;
            this.uploadId = uploadId;
        }
        @Override
        public void run() {
            InputStream instream = null;
            try {
                instream = new FileInputStream(this.localFile);
                instream.skip(this.startPos);

                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.setBucketName(bucketName);
                uploadPartRequest.setKey(key);
                uploadPartRequest.setUploadId(this.uploadId);
                uploadPartRequest.setInputStream(instream);
                uploadPartRequest.setPartSize(this.partSize);
                uploadPartRequest.setPartNumber(this.partNumber);

                UploadPartResult uploadPartResult = client.uploadPart(uploadPartRequest);
                synchronized (partETags) {
                    partETags.add(uploadPartResult.getPartETag());
                    WorkPartEtag workPartEtag = new WorkPartEtag();
                    workPartEtag.setUploadId(this.uploadId);
                    workPartEtag.setPartNumber(this.partNumber+"");
                    WorkPartEtag workPartEtag1 = MultipartUpload.this.workPartEtagService.getByuploadId(workPartEtag);
                    if (workPartEtag1!=null && StringUtils.isNotBlank(workPartEtag1.getId())){
                        workPartEtag1.setStatus("1");
                        workPartEtag1.setETag(uploadPartResult.getETag());
                        workPartEtag1.setSize(uploadPartResult.getPartETag().getPartSize()+"");
                        workPartEtag1.setPartCRC(uploadPartResult.getPartETag().getPartCRC()+"");
                        workPartEtagService.save(workPartEtag1);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (instream != null) {
                    try {
                        instream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 判断是否上传完毕，这里起始需要得到哪个错了，从哪个开始重新上传，获得part#，然后计算5M的大小上传
     * 这里只是单纯的记录一下，以后再完善
     * @return
     */
    public void judgePartCount(){
        if (partETags.size() != partCount) {
            System.out.println("一部分没有上传成功，需要从ListMultipartUploads里面查看上传数量");
            throw new IllegalStateException("Upload multiparts fail due to some parts are not finished yet");
        } else {
            System.out.println("上传成功，文件名称：" + key + "\n");
        }
    }

    /**
     * 验证文件是否完全，OSS必须走这个方法
     * 最后返回CompleteMultipartUploadResult，这里不继续深化了
     * @return
     */
    public void verification(){
        // 修改顺序
        Collections.sort(partETags, new Comparator<PartETag>() {
            @Override
            public int compare(PartETag p1, PartETag p2) {
                return p1.getPartNumber() - p2.getPartNumber();
            }
        });
        System.out.println("开始验证\n");
        CompleteMultipartUploadRequest completeMultipartUploadRequest =
                new CompleteMultipartUploadRequest(bucketName, key, uploadId, partETags);
        client.completeMultipartUpload(completeMultipartUploadRequest);
    }

    /**
     * 恢复初始值
     * @return
     */
    public void restoreDefaultValues(){
        executorService = Executors.newFixedThreadPool(5);
        partETags = Collections.synchronizedList(new ArrayList<PartETag>());
        client = null;
        uploadId = null;
        partCount = 0;
    }

    /**
     * 页面上进度查询走这个方法
     * @return
     */
    @ResponseBody
    @RequestMapping("u_test")
    public Object u_test(){
        //获得OSS上面的已经好了的分片和总分片相除获得进度
        ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, key, uploadId);
        PartListing partListing;
        try {
            partListing = client.listParts(listPartsRequest);
            Integer nowPartCount = partListing.getParts().size();
            return (int)((double)nowPartCount/partCount * 100);
        }catch (Exception e){
            //如果标识符为true说明已经开始上传，设为100%；否则说明刚进入方法，还没有上传，设为1%
            if(progress)
                return "100";
            else
                return "1";
        }
    }
}
