/**
 * Base prompt implementation
 * Should be extended by prompt types.
 */

var _ = require("lodash");
var clc = require("cli-color");
var ansiTrim = require("cli-color/lib/trim");
var readline = require("readline");
var utils = require("../utils/utils");
var Choices = require("../objects/choices");


/**
 * Module exports
 */

module.exports = Prompt;


/**
 * Prompt constructor
 */

function Prompt( question, rl ) {

  // Setup instance defaults property
  _.assign( this, {
    height : 0,
    status : "pending"
  });

  // Set defaults prompt options
  this.opt = _.defaults( _.clone(question), {
    validate: function() { return true; },
    filter: function( val ) { return val; },
    when: function() { return true; }
  });

  // Check to make sure prompt requirements are there
  if (!this.opt.message) {
    this.throwParamError("message");
  }
  if (!this.opt.name) {
    this.throwParamError("name");
  }

  // Normalize choices
  if ( _.isArray(this.opt.choices) ) {
    this.opt.choices = new Choices( this.opt.choices );
  }

  this.rl = rl;

  return this;
}


/**
 * Start the Inquiry session and manage output value filtering
 * @param  {Function} cb  Callback when prompt is done
 * @return {this}
 */

Prompt.prototype.run = function( cb ) {
  var self = this;
  this._run(function( value ) {
    self.filter( value, cb );
  });
  return this;
};

// default noop (this one should be overwritten in prompts)
Prompt.prototype._run = function( cb ) { cb(); };


/**
 * Throw an error telling a required parameter is missing
 * @param  {String} name Name of the missing param
 * @return {Throw Error}
 */

Prompt.prototype.throwParamError = function( name ) {
  throw new Error("You must provide a `" + name + "` parameter");
};


/**
 * Remove the prompt from screen
 * @param  {Number}  Extra lines to remove (probably to compensate the "enter" key line
 *                   return)
 * @return {Prompt}  self
 */

Prompt.prototype.clean = function( extra ) {
  _.isNumber(extra) || (extra = 0);
  var len = this.height + extra;

  while ( len-- ) {
    readline.moveCursor(this.rl.output, -clc.width, 0);
    readline.clearLine(this.rl.output, 0);
    if ( len ) readline.moveCursor(this.rl.output, 0, -1);
  }
  return this;
};


/**
 * Move cursor down by `x`
 * @param  {Number} x How far to go down (default to 1)
 * @return {Prompt}   self
 */

Prompt.prototype.down = function( x ) {
  _.isNumber(x) || (x = 1);

  // @bug: Write new lines instead of moving cursor as unix system don't allocate a new
  // line when the cursor is moved over there.
  while ( x-- ) {
    this.write("\n");
  }

  return this;
};


/**
 * Move cursor up by `x`
 * @param  {Number} x How far to go up (default to 1)
 * @return {Prompt}   self
 */

Prompt.prototype.up = function( x ) {
  _.isNumber(x) || (x = 1);
  readline.moveCursor( this.rl.output, 0, -x );
  return this;
};


/**
 * Write error message
 * @param {String} Error   Error message
 * @return {Prompt}        Self
 */

Prompt.prototype.error = function( error ) {
  readline.moveCursor( this.rl.output, -clc.width, 0 );
  readline.clearLine( this.rl.output, 0 );

  var errMsg = clc.red(">> ") +
      (error || "Please enter a valid value");
  this.write( errMsg );

  return this.up();
};


/**
 * Write hint message
 * @param {String}  Hint   Hint message
 * @return {Prompt}        Self
 */

Prompt.prototype.hint = function( hint ) {
  readline.moveCursor( this.rl.output, -clc.width, 0 );
  readline.clearLine( this.rl.output, 0 );

  if ( hint.length ) {
    var hintMsg = clc.cyan(">> ") + hint;
    this.write( hintMsg );
  }

  return this.up();
};


/**
 * Validate a given input
 * @param  {String} value       Input string
 * @param  {Function} callback  Pass `true` (if input is valid) or an error message as
 *                              parameter.
 * @return {null}
 */

Prompt.prototype.validate = function( input, cb ) {
  utils.runAsync( this.opt.validate, cb, input );
};


/**
 * Filter a given input before sending back
 * @param  {String}   value     Input string
 * @param  {Function} callback  Pass the filtered input as parameter.
 * @return {null}
 */

Prompt.prototype.filter = function( input, cb ) {
  utils.runAsync( this.opt.filter, cb, input );
};


/**
 * Return the prompt line prefix
 * @param  {String} [optionnal] String to concatenate to the prefix
 * @return {String} prompt prefix
 */

Prompt.prototype.prefix = function( str ) {
  str || (str = "");
  return "[" + clc.green("?") + "] " + str;
};


/**
 * Return the prompt line suffix
 * @param  {String} [optionnal] String to concatenate to the suffix
 * @return {String} prompt suffix
 */

Prompt.prototype.suffix = function( str ) {
  str || (str = "");
  return (str.length < 1 || /([a-z])$/i.test(str) ? str + ":" : str).trim() + " ";
};


/**
 * Generate the prompt question string
 * @return {String} prompt question string
 */

Prompt.prototype.getQuestion = function() {

  var message = _.compose(this.prefix, this.suffix)(this.opt.message);

  // Append the default if available, and if question isn't answered
  if ( this.opt.default && this.status !== "answered" ) {
    message += "("+ this.opt.default + ") ";
  }

  return message;
};


/**
 * Write a string to the stdout
 * @return {Self}
 */

Prompt.prototype.write = function( str ) {
  this.rl.output.write( str );
  return this;
};


/**
 * Hide cursor
 * @return {Prompt}   self
 */

Prompt.prototype.hideCursor = function() {
  return this.write("\033[?25l");
};


/**
 * Show cursor
 * @return {Prompt}    self
 */

Prompt.prototype.showCursor = function() {
  return this.write("\033[?25h");
};


/**
 * Remember the cursor position
 * @return {Prompt} Self
 */

Prompt.prototype.cacheCursorPos = function() {
  this.cursorPos = this.rl._getCursorPos();
  return this;
};


/**
 * Restore the cursor position to where it has been previously stored.
 * @return {Prompt} Self
 */

Prompt.prototype.restoreCursorPos = function() {
  if ( !this.cursorPos ) return;
  var line = this.rl._prompt + this.rl.line;
  readline.moveCursor(this.rl.output, -line.length, 0);
  readline.moveCursor(this.rl.output, this.cursorPos.cols, 0);
  this.cursorPos = null;
  return this;
};

