let OSS = require('ali-oss');
let fs = require('fs');
let path = require('path');
var archiver = require('archiver');
let request = require("request");
let http = require('http');

let compressPercent = 0;
let taskCodeQueue = [];
let taskDownloaderQueue = {};
let pathConfig = {
  ossPrefix: 'http://4dkankan.oss-cn-shenzhen.aliyuncs.com/',  // oss前缀
  // serverPrefix: 'http://192.168.0.208:8887/',  // 服务器前缀
  serverPrefix: 'https://www.4dage.com/download_scene/controller/',  // 服务器前缀
  localDataName: 'localData',
  layer: './',  // 层级
  rootFold: 'tmpData',   // 根目录文件夹
  staticPath: [   // 静态数据路径
    'static'
  ],
  paths: [  // images/data路径
    'images/images',
    'data/data',
    'voice/voice',
    'video/video'
  ],
  editPaths:[
    'images/images',
    'data/data',
    'voice/voice'
  ],
  filters: [],
  dynamicPath: [   // 一些使用api加载的数据, 提前写入json中
    'https://www.4dkankan.com/api/scene/getInfo'
  ],
  downloadUrlServer:'https://www.4dkankan.com/'
}

function getEditDataUrl(num,cb = ()=>{}) {

  let URL = `https://www.4dkankan.com/api/scene/getEditDataUrl?num=${num}`
  let data = ''
  request(URL,(error, response, body)=>{
    if (!error) {
      let temp = JSON.parse(body)
      if (temp.code==0) {
        data=temp.data
        cb(data)
      }
    }
  })

}


function downloader() {
  this.objArr = [];
  this.completeFolds = 0;   // 记录已经递归完成的目录, completeFolds == foldNum, 表示所有目录递归完成
  this.fileNum = 0;  // 需要下载的文件总数
  this.completeFileNum = 0;  // 已经下载完成的文件总数
  this.threadNum = 10;   // 同时并发的线程总数
  this.curActiveThread = 0;
  this.sta = 0;
  this.downloadProcess = 0;  // 下载进度
  this.zipProcess = 0;   // 压缩进度
  this.sceneCode = '';   // 场景码
  this.sceneCodeArr = [] // 场景码数据
  this.snCode = '' //sn码
  this.isTiles = true;   // 是否下载tiles
  this.sceneInfo = '';   // 场景的其他json数据

  this.downloadResponse = null;
  this.timer = null;   // 定时器
  this.dirPath = path.join(__dirname, pathConfig.rootFold);

  this.foldNum = pathConfig.paths.length + pathConfig.staticPath.length;   // 记录目录的总数, 用于判断是否目录是否递归完成
  this.client = new OSS({   // 配置阿里云oss
    region: 'oss-cn-shenzhen',
    accessKeyId: 'LTAIUrvuHqj8pvry',
    accessKeySecret: 'JLOVl0k8Ke0aaM8nLMMiUAZ3EiiqI4',
    bucket: '4dkankan',
  })
}

/**
 * 创建文件夹
 * @param data 文件路径
 */
downloader.prototype.createRootFold = function (data) {
  if (!fs.existsSync(this.dirPath)) {
    fs.mkdirSync(this.dirPath);
  } else {
    // console.log('文件夹已存在');
  }
  this.dirPath = path.join(this.dirPath, '/' + data);
  // 创建文件夹目录
  if (!fs.existsSync(this.dirPath)) {
    fs.mkdirSync(this.dirPath);
  } else {
    // console.log('文件夹已存在');
  }
  console.log('-------dirPath---------',this.dirPath)
}

/**
 * 递归列举编辑页面需要下载的文件, 记录文件夹以及所有文件的总数
 */

downloader.prototype.listEditDir = async function (editUrl) {
  let that = this;

  editUrl.forEach(item=>{
    let dirArr = item.split('/')
    let objName = dirArr.pop()
    let tmpDir = '';
    let childDirPath;

    dirArr.forEach((sub) => {  // 对于路径中的每一个层级, 都创建对应的文件夹
      tmpDir += '/' + sub;
      childDirPath = path.join(this.dirPath, '/' + tmpDir);  // 子路径
      if (!fs.existsSync(childDirPath)) {
        fs.mkdirSync(childDirPath);
        // console.log('创建文件夹成功:  ' + childDirPath);
      } else {
        // console.log('文件夹已存在');
      }
    });

    let downloadURL = pathConfig.downloadUrlServer + item;   // 记录每个文件需要下载的url
    if (objName === '') {  // 过滤非法文件, 因为阿里云有时候会把文件夹当作文件返回, 
      return;   
    }
    let writeURL = path.join(childDirPath, objName);   // 下载完成后, 应当写入的路径
    let tack = {   // 创建下载任务
      downloadURL: downloadURL,
      writeURL: writeURL
    }

    that.objArr.push(tack);

    // this.completeFolds++;
    // this.timer = setInterval(this.download.bind(this), 16);   // 开始下载
  })
  
}

/**
 * 递归列举所有需要下载的文件, 记录文件夹以及所有文件的总数
 */
downloader.prototype.listDir = async function (dir, prefixDir) {
  let that = this;
  let filters = pathConfig.filters
  if (!that.isTiles) {
    filters.push('tiles')
  }

  
  for (let i = 0; i < filters.length; i++) {   // 有些文件夹不需要下载, 则进行过滤
    let filter = filters[i];
    if (dir.indexOf(filter) > 0) {
      console.log('过滤的路径' + dir);
      that.completeFolds++;
      if (that.completeFolds === that.foldNum) {   // 已经列举完成的文件夹数目等于需要下载的文件夹总数
        that.fileNum = that.objArr.length;
        that.timer = setInterval(that.download, 16);  // 开始下载
      }
      return;
    }
  }


  dirArr = `${prefixDir}${dir}`.split('/');   // 分割其目录层级
  let tmpDir = '';
  let childDirPath;
  dirArr.forEach((item) => {  // 对于路径中的每一个层级, 都创建对应的文件夹
    tmpDir += '/' + item;
    childDirPath = path.join(this.dirPath, '/' + tmpDir);  // 子路径
    if (!fs.existsSync(childDirPath)) {
      fs.mkdirSync(childDirPath);
      // console.log('创建文件夹成功:  ' + childDirPath);
    } else {
      // console.log('文件夹已存在');
    }
  });



  // 列举当前文件夹中的所有文件, 最多1000个
  let result = await this.client.list({
    prefix: dir,
    delimiter: '/',
    'max-keys': 1000 // 最大限制1000
  });
  // result.objects就是当前文件夹下的所有文件
  result.objects && result.objects.forEach(async function (obj) {
    let downloadURL = pathConfig.ossPrefix + obj.name;   // 记录每个文件需要下载的url
    let arr = obj.name.split('/');
    let objName = arr[arr.length - 1];   // 获取文件名, 上面的obj.name除了文件名, 还包含了文件所属的路径
    if (objName === '') {  // 过滤非法文件, 因为阿里云有时候会把文件夹当作文件返回, 
      return;   
    }
    let writeURL = path.join(childDirPath, objName);   // 下载完成后, 应当写入的路径
    let tack = {   // 创建下载任务
      downloadURL: downloadURL,
      writeURL: writeURL
    }

    that.objArr.push(tack);   // 下载任务入列
  });

  if (result.prefixes) {   // 如果当前目录还有子目录存在, 则继续递归
    this.foldNum--;    // 移除当前目录, 因为当前目录还不是最深层级
    let that = this;
    result.prefixes.forEach(function (subDir) {
      that.foldNum++;  // 记录当前目录的子目录
      that.listDir(subDir, prefixDir);
    });
  } else {   // 当前目录已经递归完成
    this.completeFolds++;
    console.log(this.completeFolds + '  ' + this.foldNum)
    if (this.completeFolds === this.foldNum) {  // 已经列举完成的文件夹数目等于需要下载的文件夹总数
      // console.log(this.objArr)
      this.fileNum = this.objArr.length;   // 记录需要下载的文件总数
      this.timer = setInterval(this.download.bind(this), 16);   // 开始下载
    }
  }
}

downloader.prototype.download = function () {

  if (this.objArr.length === 0) {   // 所有文件已经下载完成, 清楚定时器
    clearInterval(this.timer);
    this.timer = null;
    return;
  }
  if (this.curActiveThread <= this.threadNum) {    // 当前活跃的线程数<总线程数, 则继续创建创建下载队列, 控制并发
    let task = this.objArr.shift();   // 取队列中的第一个任务
    let stream = fs.createWriteStream(task.writeURL);   // 根据任务中的写入路径创建流
    let readStream = request(task.downloadURL).on('error', function () {   // 通过下载路径去请求文件
      console.log("文件[" + task.downloadURL + "]下载出错  ");
    }).pipe(stream);

    let that = this;
    readStream.on('finish', function () {  // 当前流下载完成, 释放线程
      that.curActiveThread--;  
    })
    stream.on('finish', function () {   // 文件写入完毕
      that.completeFileNum++;   // 已经下载完成的文件数+1
      that.downloadProcess = that.completeFileNum / that.fileNum * 100;   // 计算下载进度
      if (that.completeFileNum === that.fileNum) {   // 所有的oss文件已下载完
        let sceneCode = that.sceneCode;  // 记录场景码
        let sceneCodeArr = that.sceneCodeArr;  // 记录场景码数组
        let snCode = that.snCode

        let sceneCodePath = path.join(__dirname, `tmpData/${snCode}/code.txt`);

        fs.writeFileSync(sceneCodePath , '')
        
        sceneCodeArr.forEach((item,idx)=>{
            // sceneData.json的写入路径, sceneData.json记录的是场景中的其他信息, 如场景描述、场景密码等可配置信息
            let sceneJsonPath = path.join(__dirname, `tmpData/${snCode}/static/images/images${item.sceneCode}/sceneData.json`);  
            // code.txt的写入路径, 其记录的是场景码, 本地浏览器需要读取该文件
            
            fs.writeFileSync(sceneJsonPath, item.sceneInfo, function (err) {  // 写入sceneInfo.json
              if (err) {
                return console.log(`写入文件出错: ${err}`);
              }
            })
            fs.writeFileSync(sceneCodePath , item.sceneCode+(idx===sceneCodeArr.length-1?'':','),{flag:'a'}, function (err,code) {   // 将场景码写入
                console.log('-----code-----',code)
            })
        })

        console.log('开始压缩' + that.sceneCode)
        // let zipStream = fs.createWriteStream(`${__dirname}/tmpData/${that.sceneCode}/` + `/localData.zip`);
        let zipStream = fs.createWriteStream(`${__dirname}/tmpData/zip/` + `/${snCode}.zip`);  // 创建压缩包
        pathConfig.localDataName = snCode;

        let archive = archiver('zip', {
          zlib: {
            level: 9  // 压缩等级
          }
        });
        zipStream.on('close', function () {
          let respData = 'archiver has been finalized and the output file descriptor has closed.';
          console.log(archive.pointer() + ' total bytes');
          console.log('文件压缩已写入完成');
          // 重置两个进度
          downloadPercent = 0;
          compressPercent = 0;
        });
        // 计算压缩进度
        archive.on('progress', function (process) {
          compressPercent = that.zipProcess = process.fs.processedBytes / process.fs.totalBytes * 100;
        })
        archive.on('error', function (err) {
          console.log(err);
        })
        archive.pipe(zipStream);
        archive.directory(`${__dirname}/tmpData/${snCode}/`, false);  // 将已经下载完成的全部内容放入至压缩流中
        archive.directory(`${__dirname}/static/page`, false);  // 追加html文件
        archive.directory(`${__dirname}/browser/`, false);  // 将浏览器放入压缩流中
        archive.finalize();
      }
    })
    this.curActiveThread++;  // 激活的线程数+1
  }
}

downloader.prototype.execute = function (data) {
  // let arr = ['t-updScXn','t-J3VGU9J']
  this.createRootFold(this.snCode);
  this.sceneCodeArr.forEach(item => {
    getEditDataUrl(item.sceneCode, (res)=>{
      this.listEditDir(res);
      pathConfig.paths.forEach(path => {
        this.listDir(path + item.sceneCode, `static/`);
      })
      pathConfig.staticPath.forEach(path => {
        this.listDir(path, '');
      })
    })
  })
  
}

/**
 * 起始函数
 * @param {*} response 响应
 * @param {*} data 前端传过来的数据
 */
function start(response, data) {
  let sceneCode = data.sceneCode;
  let sceneCodeArr = data.sceneCodeArr
  let snCode = data.snCode
  let respData;
  // 如果已经存在压缩包, 则直接返回压缩包的路径给浏览器进行下载
  if (fs.existsSync(`${__dirname}/${pathConfig.rootFold}/${snCode}/${pathConfig.localDataName}.zip`)) {
    respData = {
      sta: 1003,   // 1003 文件已存在
      data: {
        percent: 100,
        url: `${pathConfig.serverPrefix}${pathConfig.rootFold}/${snCode}/${pathConfig.localDataName}.zip`
      },
      msg: '文件已存在, 直接返回url'
    };
    // console.log('文件已存在, 直接返回url');
  } else {
    respData = {
      sta: 1002,   // 1002 
      data: {
        percent: 0
      },
      msg: '已加入任务队列'
    };
    taskCodeQueue.push(snCode);
    taskDownloaderQueue[snCode] = new downloader();   // 对于每个前端发来的下载请求, 创建一个downloader实例

    // taskDownloaderQueue[snCode].sceneCode = data.sceneCode;
    taskDownloaderQueue[snCode].sceneCodeArr = sceneCodeArr;
    taskDownloaderQueue[snCode].snCode = snCode;


    // taskDownloaderQueue[snCode].sceneCode = data.sceneCode;
    taskDownloaderQueue[snCode].isTiles = data.isTiles;
    // taskDownloaderQueue[snCode].sceneInfo = data.sceneInfo;  // sceneInfo就是场景中其他的可配置信息, 用于写入sceneData.json中
  }
  let json = JSON.stringify(respData)
  response.send(json);
}

/**
 * 查询下载进度
 * @param {*} response 响应
 * @param {*} data 前端传过来的数据
 */
function downloadProcess(response, data) {
  let snCode = data.snCode;
  let downloader = taskDownloaderQueue[snCode];
  if (!downloader) return;
  if (downloader.downloadProcess < 100) {   // 文件正在下载中
    let respData = {
      sta: 1000,   // 状态码 1000-文件正在下载 1001-文件正在压缩
      data: {
        percent: downloader.downloadProcess
      },
      msg: '文件下载中'
    };
    let json = JSON.stringify(respData)
    response.send(json);
  } else {  // 文件正在压缩中
    let respData = {
      sta: 1001,   // 状态码 1000-文件正在下载 1001-文件正在压缩
      data: {
        percent: downloader.zipProcess
      },
      msg: '文件压缩中'
    };
    if (downloader.zipProcess === 100) {
      respData.data.url = `${pathConfig.serverPrefix}${pathConfig.rootFold}/zip/${pathConfig.localDataName}.zip`
    }
    let json = JSON.stringify(respData)
    response.send(json);
  }
}

(function () {
  // 服务器的downloader队列, 每隔1s处理一个前端的下载请求, 避免服务器压力过大

  setInterval(function () {
    let taskCode = taskCodeQueue.shift();
    if (taskCode) {
      taskDownloaderQueue[taskCode].execute(taskCode);
    }
  }, 1000)
})()

exports.start = start;
exports.downloadProcess = downloadProcess;

