'use strict';

// GLOBAL ONLY !
const fs = require('fs');
const fse = require('fs-extra');
const path = require('path');
const glob = require('glob');
const childProcess = require("child_process");
const execSync = childProcess.execSync;
const semver = require('semver');
const chalk = require('chalk');
const figlet = require('figlet');
const figures = require('figures');
const spawn = require('cross-spawn');
const eol = require('eol');
const execa = require('execa');
const replace = require('replace-in-file');
const xml2js = require('xml2js');
const moment = require('moment');

const isTestRunning = process.env._TEST;

function runScript(_args, cwdFolder) {
    const args = _args.replace(/\s+/g, ' ').split(' ');
    const binary = args.shift();

    return new Promise((resolve, reject) => {
        const child = spawn(
            binary,
            args, { stdio: 'inherit', cwd : cwdFolder }
        );

        child.on('exit', (code) => {
            if (code !== 0) {
                return reject(new Error(`Error executing script: "${binary} ${args.join(' ')}"`));
            }
            resolve();
        });
    });
}

async function runNodeScriptProcessOutput(_args, cwdFolder) {
  const { error, stdout, stderr } = await execa('node', _args, { cwd: cwdFolder });

  return {
    error, stdout, stderr
  };
}

function getPackages(rootFolder) {
    if (!rootFolder) {
        rootFolder = 'src/packages';
    }
    logInfo('-- Getting packages from ' + rootFolder);

    const packages = fs.readdirSync(rootFolder)
                       .filter(name => fs.lstatSync(path.resolve(rootFolder, name)).isDirectory())
                       .map(name => {
                           return { 'package': name }
                       });

    if (packages.length === 0) {
        logInfo('----> no packages found');
    } else {
        logInfo('----> packages found : ' + packages.map(p => p.package));
    }
    return packages;
}

function getArgs() {
  const argv = require('yargs').argv;

  const processArgs = process.env._CSDR_ARGS;

  let args = {};

  if (processArgs) {
    processArgs.split(',').forEach((item) => {
      // check if it's defined as array - example format : someArray:test1=test1|test2=test2
      if (item.indexOf('Array') > 0) {
        const arrayItems = item.split(':')[1];
        const subItems = arrayItems.split('|');
        const array = [];
        subItems.forEach((sub) => {
          const subItemSplit = sub.split('=');
          const newItem = {};
          newItem[subItemSplit[0].trim()] = subItemSplit[1].trim();
          array.push(newItem);
        })
        args[item.split(':')[0]] = array;

      // otherwise normal key/value pairs - example format : test1:test1,test2:test2
      } else {
        const itemSplit = item.split(':');
        args[itemSplit[0].trim()] = itemSplit[1].trim();
      }
    })
  }

  return {
    root: argv._[0],
    ...argv,
    ...args
  }
}


function convertBooleanArg(arg) {
  if (arg && typeof(arg) === 'boolean') {
    return false;
  } else {
    if (arg) {
      return true;
    } else {
      return false;
    }
  }
}

function convertStringArg(arg, defaultValue = null) {
  if (arg && typeof(arg) === 'boolean') {
    return defaultValue
  } else {
    if (arg) {
      return arg;
    } else {
      return defaultValue;
    }
  }
}


/* -------------------------------------------------------------------------------------------
**  FILES OPERATIONS
** ------------------------------------------------------------------------------------------- */

// TODO DETECT if folder exists, otherwise it's exiting without continuing the promise chain
function relativeCopy(fileGlob, from, to) {
  return new Promise((resolve, reject) => {
      glob(fileGlob, { cwd: from, nodir: true, follow: true, dot: true }, (err, files) => {
          if (err) {
            logError(err);
            reject(err);
          }
          files.forEach(file => {
              const origin = path.join(from, file);
              const dest = path.join(to, file);
              _recursiveMkDir(path.dirname(dest));
              fse.copy(origin, dest);
              resolve();
          })
      })
  });
}

function relativeCopyEol(fileGlob, from, to) {
  return new Promise((resolve, reject) => {
      glob(fileGlob, { cwd: from, nodir: true, follow: true, dot: true }, (err, files) => {
          if (err) reject(err);
          files.forEach(file => {
              const origin = path.join(from, file);
              const dest = path.join(to, file);
              _recursiveMkDir(path.dirname(dest));
              //fse.copy(origin, dest);
              const content = fs.readFileSync(origin, { encoding: 'utf8' });
              const contentToCRLF = eol.crlf(content);
              fs.writeFileSync(dest, contentToCRLF);
              resolve();
          })
      })
  });
}

function copydir(from, to, overwrite = true, globPattern = '**/*') {
  logInfo('----->copydir from : ' + from + ' ====> to : ' + to );
  var files = glob.sync(globPattern, { cwd: from, nodir: true, follow: true, dot: true });
  files.forEach(file => {
      const origin = path.join(from, file);
      const dest = path.join(to, file);
      _recursiveMkDir(path.dirname(dest));
      fse.copySync(origin, dest, { overwrite: overwrite });
  });
}

function copydirFiles(from, to, overwrite = true, globPattern = '**/*') {
  logInfo('----->copydir files from : ' + from + ' ====> to : ' + to );
  var files = glob.sync(globPattern, { cwd: from, nodir: true, follow: true, dot: true });
  files.forEach(file => {
      const origin = path.join(from, file);
      const dest = path.join(to, file);
      fse.copySync(origin, dest, { overwrite: overwrite });
  });
}

function rmdir(path) {
  logInfo('----> remiving ' + path);
  try {
      if (isWindows()) {
          fs.rmdirSync(path);
      } else {
          fs.unlinkSync(path);
      }
  } catch(e) {
  }
}

function rimraf(inputPath) {
  this.logInfo(`Removing ${inputPath}...`);
  let exec = path.resolve(process.cwd(), 'node_modules', 'rimraf', 'bin.js');
  if (!isFileExists(exec)) {
      // special location when used within eUI CLI
      exec = path.resolve(__dirname, '../../../..', 'rimraf', 'bin.js');
  }
  return execa.sync('node', [exec, inputPath], { cwd: process.cwd(), stdio: 'inherit' })
}

function rmdirFull(dirPath, removeSelf) {
  try {
      if (removeSelf === undefined) {
        removeSelf = true;
      }

      try {
        var files = fse.readdirSync(dirPath);
      }
      catch(e) {
        this.logWarning('Error trying to read folder files');
        console.log(e);
        return;
      }

      if (files.length > 0) {
        for (var i = 0; i < files.length; i++) {
            var filePath = path.join(dirPath, files[i]);
            if (fse.statSync(filePath).isFile())
            fse.unlinkSync(filePath);
            else
            rmdir(filePath);
        }
      }

      if (removeSelf) {
        fse.rmdirSync(dirPath);
      }
  }

  catch(e) {
    this.logWarning('Error trying to remove folder content');
    console.log(e);
    return;
  }
};

function mkdir(path) {
  if (!fs.existsSync(path)) {
      fs.mkdirSync(path);
  }
}

function mkdirRecursive(path) {
  if (!fs.existsSync(path)) {
      _recursiveMkDir(path);
  }
}

function isDirExists(path) {
  try {
      return fs.statSync(path).isDirectory();
  } catch (err) {
      return false;
  }
}

function isDirEmpty(path) {
  try {
    var files = fse.readdirSync(path);

    if (files.length === 0) {
      return true;
    } else {
      return false;
    }

  } catch (err) {
    return false;
  }
}

function isFileExists(path) {
  try {
      return fs.statSync(path);
  } catch (err) {
      return false;
  }
}

function link(src, target) {
  rmdir(target);
  if (isWindows()) {
      execSync(`mklink /D ${target} ${src}`);
  } else {
      execSync(`ln -s ${src} ${target}`);
  }
}

function _recursiveMkDir(dir) {
    if (!fs.existsSync(dir)) {
        _recursiveMkDir(path.dirname(dir));
        fs.mkdirSync(dir);
    }
}

function writeJsonFile(filename, data) {
    return new Promise((resolve, reject) => {
            fs.writeFile(filename, JSON.stringify(data, null, 4), function (err) {
                if (err) reject(err);
                resolve();
            });
    });
}

function writeJsonFileSync(filename, data) {
  this.mkdirFilePath(filename);
  fs.writeFileSync(filename, JSON.stringify(data, null, 4));
}

function getJsonFileContent(filename) {
  var content = {};

  if (isFileExists(filename)) {
    content = require(filename);
  }

  return content;
}

function getFileContent(filename) {
  return fs.readFileSync(filename, { encoding: 'utf8' });
}

function writeFileContent(filename, content) {
  this.mkdirFilePath(filename);
  return fs.writeFileSync(filename, content);
}

function getLinesCount(filename) {
  const data = fs.readFileSync(filename);
  const res = data.toString().split('\n').length;
  return res - 1;
}

function getPackageFolderStats(folder, globPattern = '**/*') {
  try {

  const files = glob.sync(globPattern, { cwd: folder, nodir: true, follow: true, dot: false });
  let totalCount = {
    total: {
      files: 0,
      lines: 0,
    },
  };

  files.forEach(file => {
    if (file.lastIndexOf('.') > -1) {
      const fileExt = file.substr(file.lastIndexOf('.') + 1);
      const fullFilePath = path.join(folder, file);
      const fileLinesCount = getLinesCount(fullFilePath);

      if (totalCount[fileExt]) {
        totalCount[fileExt].files++;
        totalCount[fileExt].lines += fileLinesCount;
      } else {
        totalCount[fileExt] = {};
        totalCount[fileExt].files = 1;
        totalCount[fileExt].lines = fileLinesCount;
      }

      totalCount['total'].files++;
      totalCount['total'].lines += fileLinesCount;
    }
  });

  return totalCount;

  } catch(e) {
    tools.logError('Unable to count folder stats');
    console.log(e);

  }
}

function copy(origin, dest) {
  if (isFileExists(origin)) {
    fse.copySync(origin, dest);
  } else {
    logWarning(`There is no file to copy at ${origin}`);
  }
}

function remove(path) {
    fse.removeSync(path);
}

function move(from, to) {
    fse.removeSync(to);
    fse.moveSync(from, to, );
}

function mkdirFilePath(filename) {
  const metadataFilePath = path.dirname(filename);
  try {
    if (!this.isDirExists(metadataFilePath)) {
      fs.mkdirSync(metadataFilePath, { recursive: true })
    }
  } catch(e) {
    if (e.code === 'EEXIST') return;
    throw e;
  }
}

function replaceInFile(file, fromString, toString) {
  const fromRegExp = new RegExp(fromString, 'g');

  logInfo('replace in file: ' + file);

  return new Promise((resolve, reject) => {
    replace({
      files: file,
      from: fromRegExp,
      to: toString,
      disableGlobs: true,
    }, function (err, response) {
      if (err) {
        return reject(err);
      } else {
        return resolve(response);
      }
    })
  })
}

function replaceInFileSync(file, fromString, toString) {
  const fromRegExp = new RegExp(fromString, 'g');

  logInfo('replace in file: ' + file);

  return replace.sync({
    files: file,
    from: fromRegExp,
    to: toString,
    disableGlobs: true,
  });
}

function replaceInPath(rootPath, fromString, toString) {
  const fromRegExp = new RegExp(fromString, 'g');

  return replace.sync({
    glob: { dot: true },
    files: [rootPath + '/**/*.*'],
    from: fromRegExp,
    to: toString,
  });
}

function getXMLJsContent(file) {
  const content = fs.readFileSync(file, { encoding: 'utf8' });

  var output;
  xml2js.parseString(content, function (err, result) {
    output = result;
  });

  return output;
}

function getFolders(parentPath) {
  if (isDirExists(parentPath)) {
    return fs.readdirSync(parentPath).filter(f => fs.statSync(path.join(parentPath, f)).isDirectory());
  } else {
    return [];
  }
}

function getFiles(parentPath) {
  return fs.readdirSync(parentPath).filter(f => fs.statSync(path.join(parentPath, f)).isFile());
}

function getFilesGlob(from, fileGlob) {
  return new Promise((resolve, reject) => {
    glob(fileGlob, { ignore: ['dist/**'], cwd: from, nodir: true, follow: true, dot: true }, (err, files) => {
        if (err) {
          logError(err);
          reject(err);
        }
        return resolve(files);
    })
  });
}

function removeFilesExtension(rootPath, extension) {
  return new Promise((resolve, reject) => {
    glob(rootPath + "/**/*" + extension,
      function (err, files) {
        if (err) {
          return reject(err);
        } else {
          var processed = 0;
          files.forEach(function (file) {
            const dir = path.dirname(file);
            const filename = path.basename(file);
            logInfo('remove extention for file : ' + filename + ' in ' + dir);
            const filenameUpdate = filename.substr(0, filename.indexOf(extension));
            fs.renameSync(file, dir + "/" + filenameUpdate);
            processed++;
          });
          logInfo(processed + " files processed");
          resolve();
        }
      });
  })
}



/* -------------------------------------------------------------------------------------------
**  STRING and ARRAY
** ------------------------------------------------------------------------------------------- */

function capitalizeFirstLetter(string) {
    return string.charAt(0).toUpperCase() + string.slice(1);
}

function camelCaseString(string, separator, first) {
    var stringArray = string.split(separator);
    var result = '';
    var s;

    for (var i=0; i<stringArray.length;i++) {
        s = stringArray[i];
        if (i == 0) {
            if (first) {
                result += s.substring(0,1).toUpperCase() + s.substr(1);
            } else {
                result += s;
            }
        } else {
            result += s.substring(0,1).toUpperCase() + s.substr(1);
        }
    }

    return result;
}

function replaceAll(str, find, replace) {
  return str.replace(new RegExp(find, 'g'), replace);
}

function compareValues(key, order='asc') {
  return function(a, b) {
    if(!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) {
        return 0;
    }

    const varA = (typeof a[key] === 'string') ?
      a[key].toUpperCase() : a[key];
    const varB = (typeof b[key] === 'string') ?
      b[key].toUpperCase() : b[key];

    let comparison = 0;
    if (varA > varB) {
      comparison = 1;
    } else if (varA < varB) {
      comparison = -1;
    }
    return (
      (order == 'desc') ? (comparison * -1) : comparison
    );
  };
}

function sortArray(arr, key, order) {
  return arr.sort(compareValues(key, order));
}

function removeArrayDuplicates(myArr, prop) {
  return myArr.filter((obj, pos, arr) => {
    if (prop) {
      return arr.map(mapObj => mapObj[prop]).indexOf(obj[prop]) === pos;
    } else {
      return arr.map(mapObj => mapObj).indexOf(obj) === pos;
    }
  });
}

function getArraySum(myArr, prop) {
  return myArr.reduce((prev, cur) => {
    return prev + cur[prop];
  }, 0);
}

function splitNpmPkg(npmPkg) {
  const scope = npmPkg.substr(0, npmPkg.indexOf('/'));
  const name = npmPkg.substr(npmPkg.indexOf('/') + 1);

  return { scope: scope, name: name };
}







/* -------------------------------------------------------------------------------------------
**  SYSTEM
** ------------------------------------------------------------------------------------------- */
function isWindows() {
  if (path.sep === "\\") {
      return true;
  } else {
      return false;
  }
}

function getSysConfig() {
    logInfo('Getting system infos...');

    const infos = {};

    const node = execSync('node -v').toString().trim();

    infos.node = semver.clean(node);

    try {
        infos.yarn = execSync('yarn -v').toString().trim();
    } catch(e) {
        infos.yarn = null;
    }

    infos.httpsProxy = execSync('npm config get https-proxy').toString().trim();
    const registry = execSync('npm config get registry').toString().trim();

    logSuccess();

    return infos;
}

function isSysConfigValid() {
    const infos = getSysConfig();

    const validVersions = {
        node: '>=12.20.x',
    };

    logTitle('Checking system infos...');

    if (!semver.satisfies(infos.node, validVersions.node)) {
        logError(`---> Invalid NodeJS version : found:${infos.node} - required:${validVersions.node}`);
        return false;
    }

    logSuccess();

    return true;
}







/* -------------------------------------------------------------------------------------------
**  LOGGING
** ------------------------------------------------------------------------------------------- */

function logBigTitle(text) {
  console.log('\n\n');
  console.log(
    chalk.yellow(
      figlet.textSync(text, { horizontalLayout: 'full' })
    )
  );
  console.log('\n');
}

const logTitle = (msg) => {
  log(msg, 0);
}

const logInfo = (msg) => {
  log(msg, 1);
}

const logSuccess = (msg) => {
  if (!msg) msg = 'OK';
  log(msg, 2);
}

const logError = (msg) => {
  log(msg, 3);
}
const logErrorTrace = (filename, method, e) => {
  log(`ERROR in ${filename}`, 3);
  log(`...method : ${method}`, 3);
  log(`...message : ${e.message}`, 3);
}

const logWarning = (msg) => {
  log(msg, 4);
}

const logAccent = (msg) => {
  log(msg, 5);
}

const logBanner = (msg) => {
  log(msg, 6);
}

const logDebug = (msg) => {
  if (getArgs().debug) {
    log(msg, 7);
  }
}
const logDebugTrace = (filename, method, e) => {
  if (getArgs().debug) {
    log(`ERROR in ${filename}`, 7);
    log(`...method : ${method}`, 7);
    log(`...message : ${e.message}`, 7);
  }
}
const logDebugResponse = (response) => {
  if (getArgs().debug) {
    console.log(response);
  }
}

const log = (msg, type) => {
  var prefix, msgContent, currentDateTime = moment(new Date()).format("HH:mm:ss");

  if (type === 0 || !type) {
      prefix =  figures.play;
      msgContent = `\n${currentDateTime} - ${prefix} ${msg.toUpperCase()}`;
      if (!isTestRunning) console.info(chalk.yellow(msgContent));
  }
  if (type === 1) {
      prefix = figures.pointer.repeat(3);
      msgContent = `${currentDateTime} - ${prefix} ${msg}`;
      if (!isTestRunning) console.info(chalk.cyan(msgContent));
  }
  if (type === 2) {
      prefix = figures.pointer.repeat(6);
      msgContent = `${currentDateTime} - ${prefix} ${msg}\n`;
      if (!isTestRunning) console.info(chalk.green(msgContent));
  }
  if (type === 3) {
      prefix = figures.pointer.repeat(6);
      msgContent = `${currentDateTime} - ${prefix} ${msg}`;
      console.info(chalk.red(msgContent));
  }
  if (type === 4) {
      prefix = figures.pointer.repeat(6);
      msgContent = `\n${currentDateTime} - ${prefix} ${msg}\n`;
      console.info(chalk.keyword('yellow')(msgContent));
  }
  if (type === 5) {
      prefix = figures.star.repeat(3);
      msgContent = `${currentDateTime} - ${prefix} ${msg}\n`;
      console.info(chalk.keyword('yellow')(msgContent));
  }
  if (type === 6) {
      prefix = figures.star.repeat(3)
      msgContent = prefix + ' ' + msg + ' ' + prefix;
      console.info('\n\n');
      console.info(chalk.keyword('yellow')(figures.star.repeat(msgContent.length)));
      console.info(chalk.keyword('yellow')(msgContent));
      console.info(chalk.keyword('yellow')(figures.star.repeat(msgContent.length)));
      console.info('\n');
  }
  if (type === 7) {
    prefix = figures.pointer.repeat(10);
    msgContent = `${currentDateTime} - ${prefix} ${msg}`;
    console.info(chalk.keyword('yellow')(msgContent));
  }
}

function logVersion() {
  try {
    const nmToolsPackageJsonPath = path.join(process.cwd(), 'node_modules', '@eui', 'tools', 'package.json');
    const jsonContent = getJsonFileContent(nmToolsPackageJsonPath);
    const version = jsonContent.version;
    logBanner(`eUI TOOLS version : ${version}`)
  } catch(e) {}
}

function isFalse(item) {
  return item === undefined || item === false;
}









module.exports.relativeCopy = relativeCopy;
module.exports.relativeCopyEol = relativeCopyEol;
module.exports.link = link;
module.exports.isDirExists = isDirExists;
module.exports.isDirEmpty = isDirEmpty;
module.exports.isFileExists = isFileExists;
module.exports.mkdir = mkdir;
module.exports.mkdirRecursive = mkdirRecursive;
module.exports.copydir = copydir;
module.exports.copydirFiles = copydirFiles;
module.exports.rmdir = rmdir;
module.exports.rmdirFull = rmdirFull;
module.exports.rimraf = rimraf;
module.exports.copy = copy;
module.exports.remove = remove;
module.exports.move = move;
module.exports.getPackages = getPackages;
module.exports.runScript = runScript;
module.exports.runNodeScriptProcessOutput = runNodeScriptProcessOutput;
module.exports.writeJsonFile = writeJsonFile;
module.exports.writeJsonFileSync = writeJsonFileSync;
module.exports.getJsonFileContent = getJsonFileContent;
module.exports.getFileContent = getFileContent;
module.exports.getLinesCount = getLinesCount;
module.exports.getPackageFolderStats = getPackageFolderStats;
module.exports.writeFileContent = writeFileContent;
module.exports.mkdirFilePath = mkdirFilePath;

module.exports.capitalizeFirstLetter = capitalizeFirstLetter;
module.exports.camelCaseString = camelCaseString;
module.exports.getSysConfig = getSysConfig;
module.exports.isSysConfigValid = isSysConfigValid;
module.exports.replaceAll = replaceAll;

module.exports.logTitle = logTitle;
module.exports.logInfo = logInfo;
module.exports.logSuccess = logSuccess;
module.exports.logError = logError;
module.exports.logErrorTrace = logErrorTrace;
module.exports.logWarning = logWarning;
module.exports.logAccent = logAccent;
module.exports.logBanner = logBanner;
module.exports.logDebug = logDebug;
module.exports.logDebugTrace = logDebugTrace;
module.exports.logDebugResponse = logDebugResponse;

module.exports.replaceInFile = replaceInFile;
module.exports.replaceInFileSync = replaceInFileSync;
module.exports.replaceInPath = replaceInPath;
module.exports.sortArray = sortArray;
module.exports.removeArrayDuplicates = removeArrayDuplicates;
module.exports.getArraySum = getArraySum;
module.exports.getArgs = getArgs;
module.exports.convertBooleanArg = convertBooleanArg;
module.exports.convertStringArg = convertStringArg;
module.exports.getXMLJsContent = getXMLJsContent;
module.exports.getFolders = getFolders;
module.exports.getFiles = getFiles;
module.exports.getFilesGlob = getFilesGlob;
module.exports.logBigTitle = logBigTitle;
module.exports.removeFilesExtension = removeFilesExtension;
module.exports.splitNpmPkg = splitNpmPkg;
module.exports.logVersion = logVersion;
module.exports.isFalse = isFalse;
