'use strict';

// (function (): void {
// const fs = require('fs'),
//   path = require('path'),
//   mime = require('mime-types'),
//   color = require('colorful'),
//   crypto = require('crypto'),
//   Buffer = require('buffer').Buffer,
//   logUtil = require('./log');
import * as fs from 'fs';
import * as path from 'path';
import * as mime from 'mime-types';
import * as color from 'colorful';
import { Buffer } from 'buffer';
import { execSync } from 'child_process';
import logUtil from './log';


const networkInterfaces = require('os').networkInterfaces();

// {"Content-Encoding":"gzip"} --> {"content-encoding":"gzip"}
function lower_keys (obj: object): object {
  for (const key in obj) {
    const val = obj[key];
    delete obj[key];

    obj[key.toLowerCase()] = val;
  }

  return obj;
};

function merge (baseObj: object, extendObj: object): object {
  for (const key in extendObj) {
    baseObj[key] = extendObj[key];
  }

  return baseObj;
};

function getUserHome(): string {
  return process.env.HOME || process.env.USERPROFILE;
}

function getAnyProxyHome(): string {
  const home = path.join(getUserHome(), '/.anyproxy/');
  if (!fs.existsSync(home)) {
    fs.mkdirSync(home);
  }
  return home;
}

function getAnyProxyPath (pathName): string {
  const home = getAnyProxyHome();
  const targetPath = path.join(home, pathName);
  if (!fs.existsSync(targetPath)) {
    fs.mkdirSync(targetPath);
  }
  return targetPath;
}

/**
 * 简易字符串render替换
 */
function simpleRender (str: string, object: object, regexp: RegExp) {
  return String(str).replace(regexp || (/\{\{([^{}]+)\}\}/g), (match, name) => {
    if (match.charAt(0) === '\\') {
      return match.slice(1);
    }
    return (object[name] != null) ? object[name] : '';
  });
};

/**
 * 读取指定目录下的子目录
 */
function filewalker (root: string, cb: Function) {
  root = root || process.cwd();

  const ret = {
    directory: [],
    file: []
  };

  fs.readdir(root, (err, list) => {
    if (list && list.length) {
      list.map((item) => {
        const fullPath = path.join(root, item),
          stat = fs.lstatSync(fullPath);

        if (stat.isFile()) {
          ret.file.push({
            name: item,
            fullPath
          });
        } else if (stat.isDirectory()) {
          ret.directory.push({
            name: item,
            fullPath
          });
        }
      });
    }

    cb && cb.apply(null, [null, ret]);
  });
};

/*
* 获取文件所对应的content-type以及content-length等信息
* 比如在useLocalResponse的时候会使用到
*/
function contentType (filepath: string): string {
  return mime.contentType(path.extname(filepath)) || '';
};

/*
* 读取file的大小，以byte为单位
*/
function contentLength (filepath: string): number {
  try {
    const stat = fs.statSync(filepath);
    return stat.size;
  } catch (e) {
    logUtil.printLog(color.red('\nfailed to ready local file : ' + filepath));
    logUtil.printLog(color.red(e));
    return 0;
  }
};

/*
* remove the cache before requiring, the path SHOULD BE RELATIVE TO UTIL.JS
*/
function freshRequire (modulePath: string): NodeModule {
  delete require.cache[require.resolve(modulePath)];
  return require(modulePath);
};

/*
* format the date string
* @param date Date or timestamp
* @param formatter YYYYMMDDHHmmss
*/
function formatDate (date: Date | number, formatter: string): string {
  let finalDate : Date;
  if (typeof date !== 'object') {
    finalDate = new Date(date);
  } else {
    finalDate = date;
  }
  const transform = function (value) {
    return value < 10 ? '0' + value : value;
  };
  return formatter.replace(/^YYYY|MM|DD|hh|mm|ss/g, (match) => {
    switch (match) {
      case 'YYYY':
        return transform(finalDate.getFullYear());
      case 'MM':
        return transform(finalDate.getMonth() + 1);
      case 'mm':
        return transform(finalDate.getMinutes());
      case 'DD':
        return transform(finalDate.getDate());
      case 'hh':
        return transform(finalDate.getHours());
      case 'ss':
        return transform(finalDate.getSeconds());
      default:
        return ''
    }
  });
};


/**
* get headers(Object) from rawHeaders(Array)
* @param rawHeaders  [key, value, key2, value2, ...]

*/

function getHeaderFromRawHeaders (rawHeaders: Array<string>) {
  const headerObj = {};
  const _handleSetCookieHeader = function (key, value) {
    if (headerObj[key].constructor === Array) {
      headerObj[key].push(value);
    } else {
      headerObj[key] = [headerObj[key], value];
    }
  };

  if (!!rawHeaders) {
    for (let i = 0; i < rawHeaders.length; i += 2) {
      const key = rawHeaders[i];
      let value = rawHeaders[i + 1];

      if (typeof value === 'string') {
        value = value.replace(/\0+$/g, ''); // 去除 \u0000的null字符串
      }

      if (!headerObj[key]) {
        headerObj[key] = value;
      } else {
        // headers with same fields could be combined with comma. Ref: https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
        // set-cookie should NOT be combined. Ref: https://tools.ietf.org/html/rfc6265
        if (key.toLowerCase() === 'set-cookie') {
          _handleSetCookieHeader(key, value);
        } else {
          headerObj[key] = headerObj[key] + ',' + value;
        }
      }
    }
  }
  return headerObj;
};

function getAllIpAddress (): Array<string> {
  const allIp = [];

  Object.keys(networkInterfaces).map((nic) => {
    networkInterfaces[nic].filter((detail) => {
      if (detail.family.toLowerCase() === 'ipv4') {
        allIp.push(detail.address);
      }
    });
  });

  return allIp.length ? allIp : ['127.0.0.1'];
};

function deleteFolderContentsRecursive(dirPath: string, ifClearFolderItself: boolean): void {
  if (!dirPath.trim() || dirPath === '/') {
    throw new Error('can_not_delete_this_dir');
  }

  if (fs.existsSync(dirPath)) {
    fs.readdirSync(dirPath).forEach((file) => {
      const curPath = path.join(dirPath, file);
      if (fs.lstatSync(curPath).isDirectory()) {
        deleteFolderContentsRecursive(curPath, true);
      } else { // delete all files
        fs.unlinkSync(curPath);
      }
    });

    if (ifClearFolderItself) {
      try {
        // ref: https://github.com/shelljs/shelljs/issues/49
        const start = Date.now();
        while (true) {
          try {
            fs.rmdirSync(dirPath);
            break;
          } catch (er) {
            if (process.platform === 'win32' && (er.code === 'ENOTEMPTY' || er.code === 'EBUSY' || er.code === 'EPERM')) {
              // Retry on windows, sometimes it takes a little time before all the files in the directory are gone
              if (Date.now() - start > 1000) throw er;
            } else if (er.code === 'ENOENT') {
              break;
            } else {
              throw er;
            }
          }
        }
      } catch (e) {
        throw new Error('could not remove directory (code ' + e.code + '): ' + dirPath);
      }
    }
  }
}

function getFreePort (): Promise<number> {
  return new Promise((resolve, reject) => {
    const server = require('net').createServer();
    server.unref();
    server.on('error', reject);
    server.listen(0, () => {
      const port = server.address().port;
      server.close(() => {
        resolve(port);
      });
    });
  });
}

function collectErrorLog (error: any): string {
  if (error && error.code && error.toString()) {
    return error.toString();
  } else {
    let result = [error, error.stack].join('\n');
    try {
      const errorString = error.toString();
      if (errorString.indexOf('You may only yield a function') >= 0) {
        result = 'Function is not yieldable. Did you forget to provide a generator or promise in rule file ? \nFAQ http://anyproxy.io/4.x/#faq';
      }
    } catch (e) {}
    return result
  }
}

function isFunc (source: object): boolean {
  return source && Object.prototype.toString.call(source) === '[object Function]';
};

/**
* @param {object} content
* @returns the size of the content
*/
function getByteSize (content: Buffer): number {
  return Buffer.byteLength(content);
};

/*
* identify whether the
*/
function isIpDomain (domain: string): boolean {
  if (!domain) {
    return false;
  }
  const ipReg = /^\d+?\.\d+?\.\d+?\.\d+?$/;

  return ipReg.test(domain);
};

function execScriptSync (cmd: string): object {
  let stdout,
    status = 0;
  try {
    stdout = execSync(cmd);
  } catch (err) {
    stdout = err.stdout;
    status = err.status;
  }

  return {
    stdout: stdout.toString(),
    status
  };
};

function guideToHomePage (): void {
  logUtil.info('Refer to http://anyproxy.io for more detail');
};


const Util = {
  lower_keys,
  merge,
  getUserHome,
  contentType,
  getAnyProxyPath,
  getAnyProxyHome,
  simpleRender,
  filewalker,
  contentLength,
  freshRequire,
  getHeaderFromRawHeaders,
  getAllIpAddress,
  getFreePort,
  collectErrorLog,
  isFunc,
  isIpDomain,
  getByteSize,
  deleteFolderContentsRecursive,
  execScriptSync,
  guideToHomePage,
  formatDate,
};

export default Util;


