import path from 'path';
import fs from 'fs';
import chalk from 'chalk';
import walk from 'walk-sync';
import mkdirp from 'mkdirp';
import rimraf from 'rimraf';
import Promise from 'bluebird';
import ui from './ui';
import isDir from '../../utils/is-dir';
import template from 'lodash/template';

const rm = Promise.promisify(rimraf);
const blueprintsDir = path.join(__dirname, '..', 'blueprints');

/**
 * The Blueprint class manages generating code from a template, or "blueprint".
 * Blueprints have three main components:
 *
 *   * `Blueprint.locals()` - used to generate data to fill in the the templates
 *   * Templates, found under `<blueprint dir>/files`. These files are copied
 *     over into the project. The can contain ERB style interpolation to inject
 *     values from the `locals` data. Filenames can also contain variables,
 *     delimited by `__variable__`
 *   * `Blueprint.postInstall()`, which runs after the copying operation is
 *     finished. This gives the blueprint a chance to perform additional steps
 *     that simple templating can't support (i.e. install an node module).
 *
 * The code generated by a blueprint can also be removed via the `destroy`
 * command. That command will only remove files that exactly match the
 * what the blueprint generates, so if you modify a file after it was generated,
 * it won't be removed.
 *
 * @class Blueprint
 * @constructor
 * @private
 * @module denali
 * @submodule cli
 */
export default class Blueprint {

  /**
   * Description of what the blueprint does. Displayed when `denali generate`
   * is run without arguments.
   *
   * @property description
   * @type String
   */
  description = null;

  params = [];

  flags = {};

  /**
   * A hook to generate data to be interpolated into the blueprint's template
   * files.
   *
   * @method locals
   * @return {Object} The data to use during templating
   */
  locals(params/* , flags */) {
    return params;
  }

  /**
   * Runs after the templating step is complete, letting you make additional
   * modifications (i.e. install a node module).
   *
   * @method postInstall
   */
  postInstall() {}

  /**
   * Runs when `denali destroy` is invoked, after the applicable template files
   * have been removed. You should clean up / reverse any changes made in
   * postInstall(), but only in a way that avoids removing user modifications.
   *
   * @method postUninstall
   */
  postUninstall() {}

  /**
   * Returns the path to this blueprints template files directory. Defaults to
   * `files/`.
   *
   * @method templateFiles
   * @return {String}
   */
  get templateFiles() {
    return path.join(blueprintsDir, this.constructor.blueprintName, 'files');
  }

  /**
   * Generate the blueprint. Generates the data to interpolate into the
   * templates, then copies the template files over into the project. Finally,
   * runs the postInstall hook.
   *
   * @method generate
   * @param {Object} args  the arguments to supply to the `locals()` method
   * @private
   */
  generate({ params, flags }) {
    let data = this.locals(params, flags);
    let dest = process.cwd();

    walk(this.templateFiles).forEach((relativepath) => {
      let absolutepath = path.resolve(path.join(this.templateFiles, relativepath));
      if (isDir(absolutepath)) {
        return null;
      }

      let filenameTemplate = template(relativepath, { interpolate: /__([\S]+)__/g });
      let destRelativepath = filenameTemplate(data);
      let destAbsolutepath = path.join(dest, destRelativepath);

      if (fs.existsSync(destAbsolutepath)) {
        return ui.info(`   ${ chalk.green('already exists') } ${ destRelativepath }`);
      }

      let contents = fs.readFileSync(absolutepath, 'utf-8');
      let contentsTemplate = template(contents);
      mkdirp.sync(path.dirname(destAbsolutepath));
      fs.writeFileSync(destAbsolutepath, contentsTemplate(data));
      ui.info(`  ${ chalk.green('create') } ${ destRelativepath }`);
    });

    this.postInstall(params, flags);
  }

  /**
   * Destroy the blueprint. Generates the data to interpolate into the
   * templates, then deletes any unmodified files that were generated by this
   * blueprint. Then runs the postUninstall hook.
   *
   * @method destroy
   * @param {Object} args  the arguments to supply to the `locals()` method
   * @private
   */
  destroy({ params, flags }) {
    let data = this.locals(params, flags);
    let dest = process.cwd();

    let filesToDelete = [];
    walk(this.templateFiles).forEach((relativepath) => {
      return filesToDelete.push(path.resolve(path.join(this.templateFiles, relativepath)));
    });

    // Get the absolute paths for the template source file and the dest file
    filesToDelete = filesToDelete.map((absolutepath) => {
      let relativepath = path.relative(this.templateFiles, absolutepath);
      let filenameTemplate = template(relativepath, { interpolate: /__([\S]+)__/g });
      let destRelativepath = filenameTemplate(data);
      let destAbsolutepath = path.join(dest, destRelativepath);
      return { destAbsolutepath, destRelativepath, absolutepath };

    // Ensure that the dest file actually exists
    }).filter(({ destAbsolutepath, destRelativepath, absolutepath }) => {
      if (isDir(absolutepath)) {
        return false;
      }
      let fileExists = fs.existsSync(destAbsolutepath);
      if (!fileExists) {
        ui.info(`  ${ chalk.grey('missing') } ${ destRelativepath }`);
      }
      return fileExists;

    // And either hasn't been altered, or the force option is being used, to
    // ensure we don't destroy code
    }).filter(({ destAbsolutepath, absolutepath, destRelativepath }) => {
      let templateSrc = fs.readFileSync(absolutepath, 'utf-8');
      let compiled = template(templateSrc);
      let destFileIsNotDirty = fs.readFileSync(destAbsolutepath, 'utf-8') === compiled(data);

      if (destFileIsNotDirty) {
        ui.info(`  ${ chalk.red('destroy') } ${ destRelativepath }`);
      } else {
        ui.info(`  ${ chalk.blue('skipped') } ${ destRelativepath }`);
      }

      return destFileIsNotDirty;
    }).map(({ destAbsolutepath }) => {
      return destAbsolutepath;
    });

    rm(filesToDelete).then(() => {
      this.postUninstall();
    });
  }

}

@@ -158,40 +158,8 @@
 f';%0A
-import Promise from 'bluebird';%0A
 impo
@@ -262,46 +262,8 @@
 ';%0A%0A
-const rm = Promise.promisify(rimraf);%0A
 cons
@@ -6120,11 +6120,8 @@
     
-rm(
 file
@@ -6133,25 +6133,47 @@
 lete
-).then(() =%3E %7B%0A  
+.forEach((file) =%3E rimraf.sync(file));%0A
     
@@ -6194,23 +6194,15 @@
 tall();%0A
-    %7D);%0A
   %7D%0A%0A%7D%0A

fix destroy command file deletion