import {injectable, inject} from 'inversify';
import {VitaFileUtil} from "../interfaces/vita-file-util";
import {
  FindFilesOptions, UnZipFileResult, UnZipFileOptions, UnZipFileStatus, CopyFileResult
} from "../interfaces/vita-options-results";
import {ForceErrorImpl, CommandUtil} from "firmament-yargs";
import {VitaSpawn} from "../interfaces/vita-spawn";
const fs = require('fs');
const find = require('find');
const path = require('path');
const async = require('async');
const mkdirp = require('mkdirp');
@injectable()
export class VitaFileUtilImpl extends ForceErrorImpl implements VitaFileUtil {

  constructor(@inject('VitaSpawn')private vitaSpawn: VitaSpawn,
              @inject('CommandUtil')private commandUtil: CommandUtil) {
    super();
  }

  findFilesSync(options: FindFilesOptions) {
    try {
      options.pattern = options.pattern || /.*/;
      options.files = options.files || [];
      options.folders = options.folders || [process.cwd()];
      options.folders.forEach(folder => {
        try {
          options.files = options.files.concat(find.fileSync(options.pattern, folder));
        } catch (err) {
        }
      });
    } catch (err) {
    }
    return options.files;
  }

  findFiles(options: FindFilesOptions,
            cb: (err: Error,
                 files?: string[])=>void) {
    this.checkCallback(cb)(null, this.findFilesSync(options));
  }

  unZipFiles(options: UnZipFileOptions,
             cbStatus: (err: Error, unZipFileStatus: UnZipFileStatus)=>void,
             cbFinal: (err: Error, unZipFileResults?: UnZipFileResult[])=>void) {
    cbStatus = this.checkCallback(cbStatus);
    cbFinal = this.checkCallback(cbFinal);
    options.deleteOriginalZipFile = options.deleteOriginalZipFile || false;
    options.zippedFiles = options.zippedFiles || [];
    options.targetFolder = options.targetFolder || '';
    options.foldersOfZippedFiles = options.foldersOfZippedFiles || [];
    options.zippedFiles = this.findFilesSync({
      pattern: /\.gz$/,
      files: options.zippedFiles,
      folders: options.foldersOfZippedFiles
    });
    if (options.targetFolder) {
      try {
        mkdirp.sync(options.targetFolder);
      } catch (err) {
        cbFinal(err);
        return;
      }
    }
    let fnArray = [];
    options.zippedFiles.forEach(inFile => {
      //We know all filenames end in '.gz' because of our search pattern. Remove
      //last 3 characters for uncompressed filename.
      let outFile = inFile.slice(0, -3);
      if (options.targetFolder) {
        outFile = path.resolve(options.targetFolder, path.basename(outFile));
      }
      fnArray.push(async.apply(this.spawnUnZipOperation.bind(this),
        inFile,
        outFile,
        options.deleteOriginalZipFile,
        cbStatus));
    });
    async.parallelLimit(fnArray, 3, (err, results) => {
      cbFinal(err, results);
    });
  }

  deleteFile(file: string) {
    fs.unlink(file);
  }

  deleteFiles(files: string[]) {
    files.forEach(file => {
      try {
        this.deleteFile(file);
        this.commandUtil.log(`Deleting: ${file}`);
      } catch (err) {
        this.commandUtil.error(err.toString());
      }
    });
  }

  copyFile(inFile: string,
           outFile: string,
           cb: (err: Error, copyFileResult: CopyFileResult)=>void) {
    let rd = fs.createReadStream(inFile);
    rd.on("error", function (err) {
      done(err);
    });
    let wr = fs.createWriteStream(outFile);
    wr.on("error", function (err) {
      done(err);
    });
    wr.on("close", function (err) {
      done(err);
    });
    rd.pipe(wr);

    function done(err) {
      if (cb) {
        cb(null, {error: err, inFile, outFile});
        cb = null;
      }
    }
  }

  private spawnUnZipOperation(inFile: string,
                              outFile: string,
                              deleteZipFile: boolean,
                              cbStatus: (err: Error, unZipFileStatus: UnZipFileStatus)=>void,
                              cbFinal: (err: Error, unZipFileResult: UnZipFileResult)=>void) {

    let testResult = '';
    this.vitaSpawn.spawnIt(
      'gunzip', ['--test', inFile],
      (err: Error, result: any) => {
      },
      (err: Error, result: any) => {
        if (err) {
          cbFinal(null, {error: err, zippedFilePath: inFile});
          return;
        }
        let listResult = '';
        this.vitaSpawn.spawnIt(
          'gunzip', ['--list', inFile],
          (err: Error, result: Uint8Array) => {
            listResult += result.toString();
          },
          (err: Error, result: any) => {
            if (err) {
              cbFinal(null, {error: err, zippedFilePath: inFile});
              return;
            }
            //SuperHACK to get the uncompressed file size from 'gunzip --list'
            let re = /(\d+)/g;
            let r = re.exec(listResult);
            let totalBytes = parseInt(r[1]);//uncompressed file size (bytes)
            let bytesWritten = 0;
            let writeFileStream = fs.createWriteStream(outFile);
            this.vitaSpawn.spawnIt(
              'gunzip', ['--stdout', inFile],
              (err: Error, result: Uint8Array) => {
                bytesWritten += result.length;
                let taskName = `UnZipping ${path.basename(outFile)}`;
                let current = bytesWritten;
                let total = totalBytes;
                cbStatus(null, {taskName, current, total});
                writeFileStream.write(result);
              },
              (err: Error, result: any) => {
                writeFileStream.close();
                if (err) {
                  cbFinal(null, {error: err, zippedFilePath: inFile});
                  return;
                }
                if (deleteZipFile) {
                  this.deleteFile(inFile);
                }
                cbFinal(err, {unzippedFilePath: outFile, zippedFilePath: inFile});
              }
            );
          }
        );
      }
    );
  }
}
