// Copyright IBM Corp. 2018,2020. All Rights Reserved.
// Node module: strong-globalize
// This file is licensed under the Artistic License 2.0.
// License text available at https://opensource.org/licenses/Artistic-2.0

import dbg = require('debug');
const debug = dbg('strong-globalize');

import * as acceptLanguage from 'accept-language';
import * as assert from 'assert';
import * as fs from 'fs';
import * as _ from 'lodash';
import * as md5 from 'md5';
import * as mkdirp from 'mkdirp';
import * as path from 'path';
import {AnyObject, ResourceTag, STRONGLOOP_GLB} from './config';

export const ENGLISH = 'en';
export const PSEUDO_LANG = 'zz';
export const PSEUDO_TAG = '♚♛♜♝♞♟';
export const MSG_TAG = 'message';
export const HELPTXT_TAG = 'helptxt';
export const AML_ALL = 'all';
export const AML_NONE = 'none';
export const AML_DEFAULT = AML_NONE;
export const BIG_NUM = 999999999999;
export const MSG_GPB_UNAVAILABLE =
  '*** Login to GPB failed or GPB.supportedTranslations error.';

const HASH_KEYS = false;
const KEY_HEADERS = ['msg'];

export function hashKeys(p: string) {
  let trailer = null;
  return !(
    headerIncluded(p, KEY_HEADERS) ||
    (trailer = getTrailerAfterDot(p)) === 'txt' ||
    trailer === 'json' ||
    trailer === 'yml' ||
    trailer === 'yaml' ||
    p.indexOf(PSEUDO_TAG) === 0
  );
}

// tslint:disable:no-any
/**
 * Supported languages in CLDR notation
 */
let TARGET_LANGS: string[] | null = null;
let MY_ROOT = process.cwd();
let INTL_DIR = path.join(MY_ROOT, 'intl');

/**
 * @param {string} Override the root directory path
 */
export function setRootDir(rootPath: string) {
  let validPath = true;
  let rootStats: fs.Stats | undefined = undefined;
  try {
    rootStats = fs.statSync(rootPath);
  } catch (e) {
    validPath = false;
  }
  assert(validPath, '*** setRootDir: Root path invalid: ' + rootPath);
  if (!rootStats!.isDirectory()) validPath = false;
  assert(
    validPath,
    '*** setRootDir: Root path is not a directory: ' + rootPath.toString()
  );
  let files: string[] | undefined = undefined;
  try {
    files = fs.readdirSync(rootPath);
  } catch (e) {
    validPath = false;
  }
  validPath = validPath && !!files;
  if (validPath) {
    let intlDirFound = false;
    files!.forEach(function (item) {
      if (intlDirFound) return;
      if (item === 'intl') intlDirFound = true;
    });
    validPath = intlDirFound;
  }
  assert(
    validPath,
    '*** setRootDir: Intl dir not found under: ' + rootPath.toString()
  );
  MY_ROOT = rootPath;
  INTL_DIR = path.join(MY_ROOT, 'intl');
}

export function getRootDir() {
  return MY_ROOT;
}

export function isRootPackage() {
  return MY_ROOT === STRONGLOOP_GLB.MASTER_ROOT_DIR;
}

export function initGlobForSltGlobalize(rootDir?: string) {
  if (STRONGLOOP_GLB.MASTER_ROOT_DIR) return;
  Object.assign(STRONGLOOP_GLB, {
    MASTER_ROOT_DIR: rootDir || getRootDir(),
    MSG_RES_LOADED: [],
  });
}

export function isLoadMessages(rootDir: string) {
  if (!STRONGLOOP_GLB.MASTER_ROOT_DIR) return false;
  if (path.resolve(rootDir) === path.resolve(STRONGLOOP_GLB.MASTER_ROOT_DIR!))
    return true;
  if (!STRONGLOOP_GLB.AUTO_MSG_LOADING) return false;
  if (STRONGLOOP_GLB.AUTO_MSG_LOADING === AML_NONE) return false;
  if (STRONGLOOP_GLB.AUTO_MSG_LOADING === AML_ALL) return true;
  const packagesToLoad = STRONGLOOP_GLB.AUTO_MSG_LOADING;
  const packageName = getPackageName(rootDir);
  const load = packagesToLoad.indexOf(packageName) >= 0;
  return load;
}

export function validateAmlValue(aml: string | string[]) {
  if (aml === AML_ALL || aml === AML_NONE) return aml;
  if (Array.isArray(aml)) {
    if (aml.length === 0) return false;
    aml.forEach(function (v) {
      if (typeof aml !== 'string') return false;
    });
    return aml;
  }
  return false;
}

export function msgFileIdHash(fileName: string, rootDir: string) {
  assert(fileName);
  rootDir = rootDir || getRootDir();
  const packageName = getPackageName(rootDir);
  const packageVersion = getPackageVersion(rootDir);
  const msgFileId = fileName + packageName + packageVersion;
  return md5(msgFileId);
}

export function registerResTag(
  fileIdHash: string,
  fileName: string,
  lang: string,
  tagType: string
) {
  assert(STRONGLOOP_GLB);
  assert(fileIdHash);
  assert(fileName);
  assert(lang);
  assert(tagType);
  if (resTagExists(fileIdHash, fileName, lang, tagType)) return false;
  const resTag: ResourceTag = {
    fileIdHash: fileIdHash,
    fileName: fileName,
    lang: lang,
    tagType: tagType,
  };
  STRONGLOOP_GLB.MSG_RES_LOADED!.push(resTag);
  return true;
}

export function resTagExists(
  fileIdHash: string,
  fileName: string,
  lang: string,
  tagType: string
) {
  assert(STRONGLOOP_GLB);
  assert(fileIdHash);
  assert(fileName);
  assert(lang);
  assert(tagType);
  const resTag = {
    fileIdHash: fileIdHash,
    lang: lang,
    tagType: tagType,
  };
  const exists = _.find(STRONGLOOP_GLB.MSG_RES_LOADED, resTag) !== undefined;
  return exists;
}

export function stripBom(str: string) {
  return str.charCodeAt(0) === 0xfeff ? str.slice(1) : str;
}

/**
 * Enumerate all JS files in this application
 * @param {Function}
 *   param.content is a UTF8 string of each JS source file.
 */
const showDotCount = 500;
const showCountCount = 10000;
let enumeratedFilesCount = 0;
let scannedFileNameHash: string[] | null = null;

export function enumerateFilesSync(
  rootDir: string,
  blackList: string[] | null,
  targetFileType: string | string[],
  verbose: boolean,
  checkNodeModules: boolean,
  callback: (content: string, filePath: string) => void
) {
  enumeratedFilesCount = 0;
  scannedFileNameHash = [];
  return enumerateFilesSyncPriv(
    rootDir,
    rootDir,
    blackList,
    targetFileType,
    verbose,
    checkNodeModules,
    callback
  );
}

export function alreadyScanned(fileName: string) {
  const realFileName = process.browser ? fileName : fs.realpathSync(fileName);
  const fileNameHash = md5(realFileName);
  if (scannedFileNameHash!.indexOf(fileNameHash) >= 0) {
    return true;
  } else {
    scannedFileNameHash!.push(fileNameHash);
    return false;
  }
}

export function enumerateFilesSyncPriv(
  currentPath: string,
  rootDir: string,
  blackList: string[] | null,
  targetFileType: string | string[],
  verbose: boolean,
  checkNodeModules: boolean,
  callback: (content: string, child: string) => void
) {
  if (!currentPath) currentPath = MY_ROOT;
  if (!rootDir) rootDir = MY_ROOT;
  currentPath = path.resolve(currentPath);
  if (alreadyScanned(currentPath)) return;
  rootDir = path.resolve(rootDir);
  blackList = Array.isArray(blackList) ? blackList : [];
  if (!Array.isArray(targetFileType)) targetFileType = [targetFileType];
  let skipDir = false;
  blackList.forEach(function (part) {
    if (typeof part !== 'string') return;
    if (currentPath.indexOf(part) >= 0) skipDir = true;
  });
  if (skipDir) {
    if (verbose) console.log('***  skipping directory:', currentPath);
    return;
  }
  let files = null;
  try {
    files = fs.readdirSync(currentPath);
  } catch (e) {
    return;
  }
  files.forEach(function (item) {
    if (item.indexOf('.') === 0) return;
    const child = path.join(currentPath, item);
    let stats = null;
    try {
      stats = fs.statSync(child);
    } catch (e) {
      return;
    }
    if (stats.isDirectory()) {
      item = item.toLowerCase();
      if (item === 'test' || item === 'node_modules' || item === 'coverage')
        return;
      enumerateFilesSyncPriv(
        child,
        rootDir,
        blackList,
        targetFileType,
        verbose,
        checkNodeModules,
        callback
      );
    } else {
      const fileType = getTrailerAfterDot(item);
      if (!fileType || targetFileType.indexOf(fileType) < 0) return;
      const content = stripBom(fs.readFileSync(child, 'utf8'));
      if (verbose) console.log('~~~ examining file:', child);
      if (checkNodeModules) {
        enumeratedFilesCount++;
        if (enumeratedFilesCount % showDotCount === 0) {
          process.stdout.write('.');
          if (enumeratedFilesCount % showCountCount === 0) {
            process.stdout.write(' ' + enumeratedFilesCount.toString() + '\n');
          }
        }
      }
      callback(content, child);
    }
  });
  if (checkNodeModules) {
    const depthRoot = directoryDepth(rootDir);
    const moduleRootPaths = resolveDependencies(currentPath, rootDir);
    if (moduleRootPaths) {
      moduleRootPaths.forEach(function (modulePath) {
        const depthModule = directoryDepth(modulePath);
        if (depthModule - depthRoot > maxDirectoryDepth()) return;
        enumerateFilesSyncPriv(
          modulePath,
          rootDir,
          blackList,
          targetFileType,
          verbose,
          checkNodeModules,
          callback
        );
      });
    }
  }
}

/**
 * @param action A function to be invoked for each target language.
 * If it returns `true`, the enumeration will be terminated.
 */
export function enumerateLanguageSync(
  action: (lang: string) => boolean | undefined
): void {
  if (!TARGET_LANGS) TARGET_LANGS = getSupportedLanguages();
  for (const lang of TARGET_LANGS!) {
    const stopEnumeration = action(lang);
    if (stopEnumeration) return;
  }
}

/**
 * @param {string} lang Supported languages in CLDR notation
 * @param {Function}
 *   If callback returns err; if err, stop enumeration.
 */
export function cloneEnglishTxtSyncDeep(rootDir?: string) {
  if (!rootDir) rootDir = MY_ROOT;
  const enDirPath = path.join(rootDir, 'intl', ENGLISH);
  mkdirp.sync(enDirPath);
  return enumerateMsgSyncPriv(
    rootDir,
    rootDir,
    ENGLISH,
    true,
    true,
    0,
    function () {}
  );
}

export function enumerateMsgSync(
  rootDir: string,
  lang: string,
  checkNodeModules: boolean,
  callback: (jsonObj: AnyObject, filePath: string) => void
) {
  return enumerateMsgSyncPriv(
    rootDir,
    rootDir,
    lang,
    checkNodeModules,
    false,
    0,
    callback
  );
}

export function enumerateMsgSyncPriv(
  currentPath: string,
  rootDir: string,
  lang: string,
  checkNodeModules: boolean,
  cloneEnglishTxt: boolean,
  clonedTxtCount: number,
  callback: (json: object, file: string) => void
) {
  assert(currentPath);
  assert(rootDir);
  assert(typeof callback === 'function');
  let intlDirectory = path.join(currentPath, 'intl');
  const langDirPath = path.join(intlDirectory, lang);
  let msgFiles = null;
  try {
    msgFiles = fs.readdirSync(langDirPath);
  } catch (e) {
    return clonedTxtCount;
  }
  const enDirPath = path.join(rootDir, 'intl', ENGLISH);
  const clonedFileNames: string[] = [];
  msgFiles.forEach(function (msgFile) {
    if (msgFile.indexOf('.') === 0) return;
    const stats = fs.lstatSync(path.join(langDirPath, msgFile));
    if (!stats.isFile()) return;
    // commented out to avoid interference with intercept-stdout in test
    // debug('enumerating...', path.join(langDirPath, msgFile));
    if (cloneEnglishTxt && lang === ENGLISH) {
      if (currentPath === rootDir) return;
      if (getTrailerAfterDot(msgFile) !== 'txt') return;
      const sourceTxtFilePath = path.join(langDirPath, msgFile);
      const filePathHash = msgFileIdHash(msgFile, currentPath);
      if (resTagExists(filePathHash, msgFile, lang, HELPTXT_TAG)) return;
      registerResTag(filePathHash, msgFile, lang, HELPTXT_TAG);
      const targetTxtFilePath = path.join(enDirPath, msgFile);
      clonedFileNames.push(msgFile);
      fs.writeFileSync(targetTxtFilePath, fs.readFileSync(sourceTxtFilePath));
      clonedTxtCount++;
      console.log('--- cloned', sourceTxtFilePath);
    } else {
      const jsonObj = readToJson(langDirPath, msgFile, lang);
      if (jsonObj) {
        callback(jsonObj, path.join(langDirPath, msgFile));
      }
    }
  });
  if (cloneEnglishTxt && lang === ENGLISH && clonedFileNames.length > 0) {
    removeObsoleteFile(enDirPath, clonedFileNames);
  }
  if (checkNodeModules) {
    const depthRoot = directoryDepth(rootDir);
    const moduleRootPaths = resolveDependencies(currentPath, rootDir);
    if (moduleRootPaths) {
      moduleRootPaths.forEach(function (modulePath) {
        const depthModule = directoryDepth(modulePath);
        if (depthModule - depthRoot > maxDirectoryDepth()) return;
        clonedTxtCount = enumerateMsgSyncPriv(
          modulePath,
          rootDir,
          lang,
          false,
          cloneEnglishTxt,
          clonedTxtCount,
          callback
        );
      });
    }
  }
  return clonedTxtCount;
}

export function removeObsoleteFile(dir: string, fileNames: string[]) {
  const files = fs.readdirSync(dir);
  files.forEach(function (file) {
    const matched = file.match(/^([0-9a-f]{32})_(.*\.txt)$/);
    if (!matched) return;
    if (fileNames.indexOf(matched[2]) >= 0) {
      console.log('--- removed', path.join(dir, file));
      fs.unlinkSync(path.join(dir, file));
    }
  });
}

export function directoryDepth(fullPath: string) {
  assert(typeof fullPath === 'string');
  return _.compact(fullPath.split(path.sep)).length;
}

export function maxDirectoryDepth() {
  let depth = parseInt(process.env.STRONGLOOP_GLOBALIZE_MAX_DEPTH!, 10);
  if (isNaN(depth)) depth = BIG_NUM;
  depth = Math.max(1, depth);
  return depth;
}

export function requireResolve(
  depName: string,
  currentDir: string,
  rootDir: string
) {
  // simulates npm v3 dependency resolution
  let depPath = null;
  let stats = null;
  try {
    depPath = path.join(currentDir, 'node_modules', depName);
    stats = fs.lstatSync(depPath);
  } catch (e) {
    stats = null;
    try {
      depPath = path.join(rootDir, 'node_modules', depName);
      stats = fs.lstatSync(depPath);
    } catch (e) {
      return null;
    }
  }
  if (!stats) return null;
  return unsymbolLink(depPath);
}

export function unsymbolLink(filePath: string): string | null {
  if (!filePath) return null;
  let stats = null;
  try {
    stats = fs.lstatSync(filePath);
  } catch (e) {
    return null;
  }
  if (!stats) return null;
  if (stats.isSymbolicLink()) {
    let realPath = null;
    try {
      realPath = process.browser ? filePath : fs.realpathSync(filePath);
    } catch (e) {
      return null;
    }
    return unsymbolLink(realPath);
  } else {
    return stats.isDirectory() ? filePath : null;
  }
}

export function resolveDependencies(
  currentDir: string,
  rootDir: string,
  moduleRootPaths?: string[]
) {
  moduleRootPaths = moduleRootPaths || [];
  const packageJson = path.join(currentDir, 'package.json');
  let deps = null;
  try {
    deps = require(packageJson).dependencies;
  } catch (e) {
    return null;
  }
  if (deps === undefined || !deps) return null;
  deps = Object.keys(deps);
  if (deps.length === 0) return null;
  deps.forEach(function (dep) {
    const depPath = requireResolve(dep, currentDir, rootDir);
    if (depPath && moduleRootPaths!.indexOf(depPath) < 0) {
      moduleRootPaths!.push(depPath);
      resolveDependencies(depPath, rootDir, moduleRootPaths);
    }
  });
  moduleRootPaths = _.uniq(_.compact(moduleRootPaths));
  return moduleRootPaths;
}

/**
 * Read a txt or json file and convert to JSON
 */
const acceptableTrailers = ['json', 'txt'];

export function readToJson(langDirPath: string, msgFile: string, lang: string) {
  const fileType = getTrailerAfterDot(msgFile);
  if (!fileType || acceptableTrailers.indexOf(fileType) < 0) return null;
  let jsonObj: any = null;
  const sourceFilePath = path.join(langDirPath, msgFile);
  if (fileType === 'json') {
    jsonObj = JSON.parse(stripBom(fs.readFileSync(sourceFilePath, 'utf-8')));
  } else {
    // txt
    const origStr = stripBom(fs.readFileSync(sourceFilePath, 'utf8'));
    jsonObj = {};
    const re = /^([0-9a-f]{32})_(.*)\.txt/;
    const results = re.exec(msgFile);

    if (results && results.length === 3) {
      // deep-extracted txt file ?
      msgFile = results[2] + '.txt';
    }

    jsonObj[msgFile] = mapPercent(JSON.parse(JSON.stringify(origStr)));
  }
  if (fileType === 'json' && HASH_KEYS && lang === ENGLISH) {
    const keys = Object.keys(jsonObj);
    keys.forEach(function (key) {
      const newKey = md5(key);
      jsonObj[newKey] = jsonObj[key];
      delete jsonObj[key];
    });
  }
  return jsonObj;
}

export function normalizeKeyArrays(keyArrays?: string | string[]) {
  // keep 0 as "0"
  if (keyArrays == null) return [];
  if (typeof keyArrays === 'string' && keyArrays.length === 0) return [];
  if (!Array.isArray(keyArrays)) return [[keyArrays.toString()]];
  const retKeyArrays: string[][] = [];
  keyArrays.forEach(function (keyArray) {
    if (keyArray === null) return;
    if (typeof keyArray === 'string' && keyArray.length === 0) return;
    if (!Array.isArray(keyArray)) {
      retKeyArrays.push([keyArray.toString()]);
      return;
    }
    const retKeyArray: string[] = [];
    keyArray.forEach(function (key) {
      if (key === null) return;
      if (typeof key === 'string' && key.length === 0) return;
      assert(
        typeof key === 'string' || typeof key === 'number',
        'type of key must be a string or a number.'
      );
      retKeyArray.push(key.toString());
    });
    if (retKeyArray.length > 0) retKeyArrays.push(retKeyArray);
  });
  return retKeyArrays;
}

export function scanJson(
  keys: string[],
  data: AnyObject,
  returnErrors?: boolean
) {
  return scanJsonPriv(keys, data, null, returnErrors);
}

export function replaceJson(keys: string[], data: AnyObject, newValues: any[]) {
  return scanJsonPriv(keys, data, newValues, false);
}

export function scanJsonPriv(
  keys: string[],
  data: AnyObject,
  newValues: any[] | null,
  returnErrors?: boolean
): string[] | AnyObject {
  if (!data || typeof data !== 'object') return [];
  if (newValues) assert(keys.length === newValues.length);
  const keyArrays = normalizeKeyArrays(keys);
  const ret: string[] = [];
  keyArrays.forEach((k, kix) => {
    let d = null;
    let err = null;
    let prevObj: {[name: string]: any} | null = null;
    let prevKey: string | null = null;
    try {
      for (let ix = 0; ix < k.length; ix++) {
        if (ix === 0) d = data;
        if (typeof d === 'string') {
          err = '*** unexpected string value ' + JSON.stringify(k);
          if (returnErrors) ret.push(err);
          else console.log(err);
          return;
        }
        prevObj = d;
        prevKey = k[ix];
        d = d![k[ix]];
      }
      if (typeof d === 'string') {
        if (newValues) prevObj![prevKey!] = newValues[kix];
        else ret.push(d);
      } else {
        err = '*** not a string value ' + JSON.stringify(k);
        if (returnErrors) ret.push(err);
        else console.log(err);
      }
    } catch (e) {
      err = '*** ' + e.toString() + ' ' + JSON.stringify(k);
      if (returnErrors) ret.push(err);
      else console.log(err);
    }
  });
  return newValues ? data : ret;
}

export function sortMsges(msgs: {[name: string]: any}) {
  const keys = Object.keys(msgs);
  const msgKeys = _.remove(keys, function (key) {
    return KEY_HEADERS.some(function (header) {
      return key.indexOf(header) === 0;
    });
  });
  const sorted: {[name: string]: any} = {};
  keys.sort().forEach(function (key) {
    sorted[key] = msgs[key];
  });
  msgKeys.sort().forEach(function (key) {
    sorted[key] = msgs[key];
  });
  return sorted;
}

/**
 * Initialize intl directory structure for non-En languages
 * intl/en must exist.
 * it returns false if failed.
 */
export function initIntlDirs() {
  let intlEnStats;
  try {
    intlEnStats = fs.statSync(path.join(INTL_DIR, ENGLISH));
  } catch (e) {
    return false;
  }
  if (!intlEnStats.isDirectory()) return false;
  if (!TARGET_LANGS) TARGET_LANGS = getSupportedLanguages();
  TARGET_LANGS.forEach(function (lang) {
    mkdirp.sync(path.join(INTL_DIR, lang));
  });
  return true;
}

/**
 * @param {string} lang Supported languages in CLDR notation
 * Returns true for 'en' and supported languages
 * in CLDR notation.
 */
export function isSupportedLanguage(lang?: string) {
  lang = lang || 'en';
  if (!TARGET_LANGS) TARGET_LANGS = getSupportedLanguages();

  return TARGET_LANGS.indexOf(lang) >= 0 || getAppLanguages().indexOf(lang) > 0;
}

/**
 * Returns an array of locales supported by the local cldr data.
 */
export function getSupportedLanguages() {
  const cldrDir = path.join(__dirname, '..', 'cldr');
  let langs: string[] = [];
  enumerateFilesSync(
    cldrDir,
    null,
    ['json'],
    false,
    false,
    (content: string, filePath: string) => {
      let cldr = null;
      try {
        cldr = JSON.parse(content);
      } catch (e) {
        throw new Error('*** CLDR read error on ' + process.platform);
      }
      const theseLangs = Object.keys(cldr.main || {});
      langs = _.concat(langs, theseLangs);
    }
  );
  return _.uniq(langs);
}

export function getAppLanguages() {
  if (STRONGLOOP_GLB && STRONGLOOP_GLB.APP_LANGS) {
    return STRONGLOOP_GLB.APP_LANGS;
  }
  return [];
}

/**
 * Returns trailer of file name.
 */
export function getTrailerAfterDot(name: string) {
  if (typeof name !== 'string') return null;
  const parts = name.split('.');
  if (parts.length < 2) return null;
  return parts[parts.length - 1].toLowerCase();
}

/**
 * Returns package name defined in package.json.
 */
export function getPackageName(root?: string) {
  return getPackageItem(root, 'name');
}

export function getPackageVersion(root?: string) {
  return getPackageItem(root, 'version');
}

export function getPackageItem(root: string | undefined, itemName: string) {
  root = root || MY_ROOT;
  let item = null;
  try {
    item = require(path.join(root, 'package.json'))[itemName];
  } catch (e) {}
  return item;
}

/**
 * @param {string} name to be checked
 * @param {Array} headersAllowed a list of strings to check
 * Returns directory path for the language.
 */
export function headerIncluded(name: string, headersAllowed: string[]) {
  if (typeof name !== 'string') return false;
  let matched = false;
  if (Array.isArray(headersAllowed)) {
    headersAllowed.forEach(function (header) {
      if (matched) return;
      matched = name.indexOf(header) === 0;
    });
  } else if (typeof headersAllowed === 'string') {
    matched = name.indexOf(headersAllowed) === 0;
  }
  return matched;
}

/**
 * @param {string} lang Supported languages in CLDR notation
 * Returns directory path for the language.
 */
export function intlDir(lang: string) {
  lang = lang || ENGLISH;
  return path.join(INTL_DIR, lang);
}

/**
 * %s is included in the string
 */
export function percent(msg: string) {
  return /\%[sdj\%]/.test(msg);
}

/**
 * %replace %s with {N} where N=0,1,2,...
 */
export function mapPercent(msg: string) {
  let ix = 0;
  const output = msg.replace(/\%[sdj\%]/g, (match) => {
    if (match === '%%') return '';
    const str = '{' + ix.toString() + '}';
    ix++;
    return str;
  });
  return output;
}

export function mapArgs(p: string, args: any[]) {
  let ix = 1;
  const output: string[] = [];
  p.replace(/\%[sdj\%]/g, (match) => {
    if (match === '%%') return '';
    let arg = args[ix++];
    if (arg === undefined) arg = 'undefined';
    if (arg === null) arg = 'null';
    output.push(match === '%j' ? JSON.stringify(arg) : arg.toString());
    return '';
  });
  return output;
}

export function repackArgs(
  args: any[] | {[name: number]: any},
  initIx: number
) {
  const argsLength = Array.isArray(args)
    ? args.length
    : Object.keys(args).length;
  if (initIx >= argsLength) return [];
  const output = [];
  for (let ix = initIx; ix < argsLength; ix++) {
    output.push(args[ix]);
  }
  return output;
}

/**
 * Get the language (from the supported languages) that
 * best matches the requested Accept-Language expression.
 *
 * @param req
 * @param globalize
 * @returns {*}
 */

export function getLanguageFromRequest(
  req: {headers: {[name: string]: string}},
  appLanguages: string[],
  defaultLanguage: string
): string {
  if (!req || !req.headers || !req.headers['accept-language']) {
    return defaultLanguage;
  }

  let languages = req.headers['accept-language'].split(',');
  for (let i = 0; i < languages.length; i++) {
    let languageWeighted = languages[i].split(';');
    languageWeighted[0] = getLangAlias(languageWeighted[0].trim());
    languages[i] = languageWeighted.join(';');
  }
  const reqLanguage = languages.join(',');
  if (!reqLanguage) {
    return defaultLanguage;
  }

  acceptLanguage.languages(appLanguages);
  const bestLanguage = acceptLanguage.get(reqLanguage);
  return bestLanguage || defaultLanguage;
}

export function myIntlDir() {
  return INTL_DIR;
}

/**
 * Load messages for the language from a given root directory
 * @param lang Language for messages
 * @param rootDir Root directory
 * @param enumerateNodeModules A flag to control if node_modules will be checked
 */
export function loadMsgFromFile(
  lang: string,
  rootDir: string,
  enumerateNodeModules?: boolean
) {
  assert(lang);
  rootDir = rootDir || getRootDir();
  if (!isLoadMessages(rootDir)) return;
  enumerateNodeModules = enumerateNodeModules || false;
  const tagType = MSG_TAG;
  enumerateMsgSync(
    rootDir,
    lang,
    enumerateNodeModules,
    (jsonObj: AnyObject, filePath: string) => {
      // writeAllToMsg(lang, jsonObj);
      let fileName = path.basename(filePath);
      const re = /^([0-9a-f]{32})_(.*)\.txt/;
      const results = re.exec(fileName);
      let fileIdHash;
      if (results && results.length === 3) {
        // deep-extracted txt file ?
        fileIdHash = results[1];
        fileName = results[2] + '.txt';
      } else {
        fileIdHash = msgFileIdHash(fileName, rootDir);
        fileName = fileName;
      }
      if (resTagExists(fileIdHash, fileName, lang, tagType)) {
        debug('*** loadMsgFromFile(res tag exists): skipping:', lang, fileName);
        return;
      }
      debug('*** loadMsgFromFile(new res tag): loading:', lang, fileName);
      removeDoubleCurlyBraces(jsonObj);
      const messages: AnyObject = {};
      messages[lang] = jsonObj;
      STRONGLOOP_GLB.loadMessages!(messages);
      registerResTag(fileIdHash, fileName, lang, tagType);
      if (STRONGLOOP_GLB.formatters!.has(lang)) {
        const formatters = STRONGLOOP_GLB.formatters!.get(lang);
        for (const key in jsonObj) {
          formatters.delete(key);
        }
      }
    }
  );
}

/**
 * Remove `{{` and `}}`
 */
export function removeDoubleCurlyBraces(json: AnyObject) {
  let count = 0;
  Object.keys(json).forEach((key) => {
    count++;
    if (typeof json[key] !== 'string') {
      // The value for `zz` pseudo code is an array, let's skip
      return;
    }
    json[key] = json[key].replace(/}}/g, '').replace(/{{/g, '');
    debug(count, key + ' : ' + json[key]);
  });
}

/**
 * If an language has alias name that SG supports, return the alias name.
 *
 * The known aliases are hard-coded to solve issue
 * https://github.com/strongloop/strong-globalize/issues/150
 * @param lang
 */
export function getLangAlias(lang: string): string {
  // The {lang: alias} pairs
  let language = _.toLower(lang) || lang;
  const ALIAS_MAP: {[lang: string]: string} = {
    'zh-cn': 'zh-Hans',
    'zh-tw': 'zh-Hant',
  };
  if (lang && ALIAS_MAP.hasOwnProperty(language)) return ALIAS_MAP[language];
  return lang;
}
