'use strict';

var fs           = require('fs');
var Promise      = require('../ext/promise');
var readFile     = Promise.denodeify(fs.readFile);
var lstat        = Promise.denodeify(fs.stat);
var chalk        = require('chalk');
var EditFileDiff = require('./edit-file-diff');
var EOL          = require('os').EOL;
var isBinaryFile = require('isbinaryfile');
var template     = require('lodash/template');
var canEdit      = require('../utilities/open-editor').canEdit;

function processTemplate(content, context) {
  var options = {
    evaluate:    /<%([\s\S]+?)%>/g,
    interpolate: /<%=([\s\S]+?)%>/g,
    escape:      /<%-([\s\S]+?)%>/g
  };
  return template(content, options)(context);
}

function diffHighlight(line) {
  if (line[0] === '+') {
    return chalk.green(line);
  } else if (line[0] === '-') {
    return chalk.red(line);
  } else if (line.match(/^@@/)) {
    return chalk.cyan(line);
  } else {
    return line;
  }
}

FileInfo.prototype.confirmOverwrite = function(path) {
  var promptOptions = {
    type: 'expand',
    name: 'answer',
    default: false,
    message: chalk.red('Overwrite') + ' ' + path + '?',
    choices: [
      { key: 'y', name: 'Yes, overwrite', value: 'overwrite' },
      { key: 'n', name: 'No, skip', value: 'skip' },
      { key: 'd', name: 'Diff', value: 'diff' }
    ]
  };

  if (canEdit()) {
    promptOptions.choices.push({ key: 'e', name: 'Edit', value: 'edit' });
  }

  return this.ui.prompt(promptOptions)
    .then(function(response) {
      return response.answer;
    });
};

FileInfo.prototype.displayDiff = function() {
  var info = this,
      jsdiff = require('diff');
  return Promise.hash({
    input: this.render(),
    output: readFile(info.outputPath)
  }).then(function(result) {
    var diff = jsdiff.createPatch(
      info.outputPath, result.output.toString(), result.input
    );
    var lines = diff.split('\n');

    for (var i = 0; i < lines.length; i++) {
      info.ui.write(
        diffHighlight(lines[i] + EOL)
      );
    }
  });
};

function FileInfo(options) {
  this.action = options.action;
  this.outputPath = options.outputPath;
  this.displayPath = options.displayPath;
  this.inputPath =  options.inputPath;
  this.templateVariables = options.templateVariables;
  this.ui = options.ui;
}

FileInfo.prototype.render = function() {
  var path = this.inputPath,
      context = this.templateVariables;
  if (!this.rendered) {
    this.rendered = readFile(path).then(function(content) {
      return lstat(path).then(function(fileStat) {
        if (isBinaryFile.sync(content, fileStat.size)) {
          return content;
        } else {
          try {
            return processTemplate(content.toString(), context);
          } catch (err) {
            err.message += ' (Error in blueprint template: ' + path + ')';
            throw err;
          }
        }
      });
    });
  }
  return this.rendered;
};

FileInfo.prototype.checkForConflict = function() {
  return new Promise(function (resolve, reject) {
    fs.exists(this.outputPath, function (doesExist, error) {
      if (error) {
        reject(error);
        return;
      }

      var result;

      if (doesExist) {
        result = Promise.hash({
          input: this.render(),
          output: readFile(this.outputPath)
        }).then(function(result) {
          var type;
          if (result.input === result.output.toString()) {
            type = 'identical';
          } else {
            type = 'confirm';
          }
          return type;
        }.bind(this));
      } else {
        result = 'none';
      }

      resolve(result);
    }.bind(this));
  }.bind(this));
};

FileInfo.prototype.confirmOverwriteTask = function() {
  var info = this;

  return function() {
    return new Promise(function(resolve, reject) {
      function doConfirm() {
        info.confirmOverwrite(info.displayPath).then(function(action) {
          if (action === 'diff') {
            info.displayDiff().then(doConfirm, reject);
          } else if (action === 'edit') {
            var editFileDiff = new EditFileDiff({info: info});
            editFileDiff.edit().then(function() {
              info.action = action;
              resolve(info);
            }).catch(function() {
              doConfirm()
                .finally(function() {
                  resolve(info);
                });
            });
          } else {
            info.action = action;
            resolve(info);
          }
        }, reject);
      }

      doConfirm();
    });
  }.bind(this);
};

module.exports = FileInfo;
