import { readFileSync, readdirSync, statSync } from 'fs';
import * as path from 'path';
import { Tree } from '@nrwl/tao/src/shared/tree';
import { join, relative } from 'path';
import { logger } from '@nrwl/tao/src/shared/logger';

const binaryExts = new Set([
  // // Image types originally from https://github.com/sindresorhus/image-type/blob/5541b6a/index.js
  '.jpg',
  '.jpeg',
  '.png',
  '.gif',
  '.webp',
  '.flif',
  '.cr2',
  '.tif',
  '.bmp',
  '.jxr',
  '.psd',
  '.ico',
  '.bpg',
  '.jp2',
  '.jpm',
  '.jpx',
  '.heic',
  '.cur',

  // Java files
  '.jar',
  '.keystore',
]);

/**
 * Generates a folder of files based on provided templates.
 *
 * While doing so it performs two substitutions:
 * - Substitutes segments of file names surrounded by __
 * - Uses ejs to substitute values in templates
 *
 * @param host - the file system tree
 * @param srcFolder - the source folder of files (absolute path)
 * @param target - the target folder (relative to the host root)
 * @param substitutions - an object of key-value pairs
 *
 * Examples:
 *
 * ```typescript
 * generateFiles(host, path.join(__dirname , 'files'), './tools/scripts', {tmpl: '', name: 'myscript'})
 * ```
 *
 * This command will take all the files from the `files` directory next to the place where the command is invoked from.
 * It will replace all `__tmpl__` with '' and all `__name__` with 'myscript' in the file names, and will replace all
 * `<%= name %>` with `myscript` in the files themselves.
 *
 * `tmpl: ''` is a common pattern. With it you can name files like this: `index.ts__tmpl__`, so your editor
 * doesn't get confused about incorrect TypeScript files.
 */
export function generateFiles(
  host: Tree,
  srcFolder: string,
  target: string,
  substitutions: { [k: string]: any }
) {
  const ejs = require('ejs');
  allFilesInDir(srcFolder).forEach((filePath) => {
    let newContent: Buffer | string;
    const computedPath = computePath(
      srcFolder,
      target,
      filePath,
      substitutions
    );

    if (binaryExts.has(path.extname(filePath))) {
      newContent = readFileSync(filePath);
    } else {
      const template = readFileSync(filePath, 'utf-8');
      try {
        newContent = ejs.render(template, substitutions, {});
      } catch (e) {
        logger.error(`Error in ${filePath.replace(`${host.root}/`, '')}:`);
        throw e;
      }
    }

    host.write(computedPath, newContent);
  });
}

function computePath(
  srcFolder: string,
  target: string,
  filePath: string,
  substitutions: { [k: string]: any }
) {
  const relativeFromSrcFolder = relative(srcFolder, filePath);
  let computedPath = join(target, relativeFromSrcFolder);
  if (computedPath.endsWith('.template')) {
    computedPath = computedPath.substring(0, computedPath.length - 9);
  }
  Object.entries(substitutions).forEach(([propertyName, value]) => {
    computedPath = computedPath.split(`__${propertyName}__`).join(value);
  });
  return computedPath;
}

function allFilesInDir(parent: string) {
  let res = [];
  try {
    readdirSync(parent).forEach((c) => {
      const child = join(parent, c);
      try {
        const s = statSync(child);
        if (!s.isDirectory()) {
          res.push(child);
        } else if (s.isDirectory()) {
          res = [...res, ...allFilesInDir(child)];
        }
      } catch (e) {}
    });
  } catch (e) {}
  return res;
}
