import React from 'react';
import classnames from 'classnames';
import * as utils from 'components/payment/utils';
import * as constants from '../constants';


class CCNumberInput extends React.Component {
  constructor() {
    super();
    this.state = {
      value: '',
      cardType: 'unknown',
    };

    this.updateState = this.updateState.bind(this);
  }

  /**
   * onKeyPress event handler.
   *
   * Handles inputs and formatting of inputted data
   */
  onKeyPress(e) {
    utils.restrictNumeric(e);
    this.restrictCardNumber(e);
    this.formatCardNumber(e);
  }

  /**
   * onKeyDown event handler.
   *
   * Deals with backspace events.
   */
  onKeyDown(e) {
    this.formatBackCardNumber(e);
  }

  /**
   * onKeyUp event handler.
   *
   * Sets the card type, if possible.
   */
  onKeyUp(e) {
    this.setCardType(e);
  }

  /**
   * Handles formatting on paste events.
   */
  onPaste(e) {
    this.reformatNumeric(e);
    this.reformatCardNumber(e);
  }

  /**
   * onChange event handler.
   *
   * Handles formatting the card number.
   */
  onChange(e) {
    this.reformatCardNumber(e);
  }

  /**
   * An all encompassing event handler for inputs.
   */
  onInput(e) {
    this.reformatCardNumber(e);
    this.setCardType(e);
  }

  /**
   * Given a card number, get the card type.
   *
   * @param  {string} number Card number
   * @return {string}        Card type (ex. 'visa', 'mastercard')
   */
  getCardType(number) {
    const card = this.cardFromNumber(number);

    if (!card) {
      return 'unknown';
    }

    return card.type;
  }

  /**
   * Set the card type appropriately in state.
   *
   * @param {Object} e HTML event
   */
  setCardType(e) {
    const target = e.target;
    const value = target.value;
    const card = this.getCardType(value);

    this.updateState(card, 'cardType');
  }

  /**
   * Given a card number, return an approriately formatted card number.
   *
   * @param  {string} number Card number
   * @return {string}        Formatted card number, according to the card type
   */
  getFormattedCardNumber(number) {
    let num = number.replace(/\D/g, '');
    const card = this.cardFromNumber(num);

    // We can't match this number yet
    if (!card) {
      return num;
    }

    const upperlength = card.length[card.length.length - 1];
    num = num.slice(0, upperlength);

    // Regex with a global (/g) flag
    if (card.format.global) {
      let _ref;
      return (_ref = num.match(card.format)) !== null ? _ref.join(' ') : null;
    }

    // Execute our card type format regex
    let groups = card.format.exec(num);

    groups.shift();
    // Filter empty groups
    groups = groups.filter(function(n) {
      return n;
    });

    return groups.join(' ');
  }

  /**
   * Simple wrapper for updating component state and parent state.
   */
  updateState(value, input = 'card') {
    if (input === 'card') {
      this.setState({value: value});
      this.props.onStateUpdate({
        card: value,
      });
    } else {
      this.setState({cardType: value});
      this.props.onStateUpdate({
        cardType: value,
      });
    }
  }

  /**
   * Simple card number reformatter.
   */
  reformatNumeric(e) {
    const value = utils.reformatNumeric(e);
    this.updateState(value);
  }

  /**
   * Helper for handling card formatting when backspace-ing
   */
  formatBackCardNumber(e) {
    const target = e.target;
    const value = target.value;

    // Return if we're not backspacing
    if (e.which !== 8) {
      return;
    }

    // Return if we're not focused at the end of the input/text
    if (target.selectionStart !== null &&
      target.selectionStart !== value.length) {
      return;
    }

    // Remove the digit & trailing space
    if (/\d\s$/.test(value)) {
      e.preventDefault();
      this.updateState(value.replace(/\d\s$/, ''));
    } else if (/\s\d?$/.test(value)) {
      // Remove digit if it ends in space & digit
      e.preventDefault();
      this.updateState(value.replace(/\d$/, ''));
    }
  }

  /**
   * Given an event, format the card number.
   */
  reformatCardNumber(e) {
    const target = e.target;
    let value = target.value;

    // Force a format
    value = this.getFormattedCardNumber(value);
    this.updateState(value);
  }

  /**
   * Card number format helper
   */
  formatCardNumber(e) {
    const digit = String.fromCharCode(e.which);
    const target = e.target;
    const value = target.value;
    const card = this.cardFromNumber(value);
    const length = (value.replace(/\D/g, '') + digit).length;
    let upperlength = 16;
    let re;

    // We only want to format numbers
    if (!/^\d+$/.test(digit)) {
      return;
    }

    if (card) {
      upperlength = card.length[card.length.length - 1];
    }

    if (length >= upperlength) {
      return;
    }

    // Return if focus isn't at the end of the text
    if (target.selectionStart && target.selectionStart !== value.length) {
      return;
    }

    if (card && card.type === 'amex') {
      // AMEX cards are formatted differently
      re = /^(\d{4}|\d{4}\s\d{6})$/;
    } else {
      // Not an AMEX card
      re = /(?:^|\s)(\d{4})$/;
    }

    // If '4242' + 4
    if (re.test(value)) {
      e.preventDefault();
      this.updateState(value + ' ' + digit);
    } else if (re.test(value + digit)) {
      // If '424' + 2
      e.preventDefault();
      this.updateState(value + digit + ' ');
    }
  }

  /**
   * On input, restrict to only numerics.
   *
   * @param  {Object} e  HTML event
   * @return {Boolean}   Restrict this input?
   */
  restrictCardNumber(e) {
    const target = e.target;
    const digit = String.fromCharCode(e.which);

    // Only check numbers
    if (!/^\d+$/.test(digit)) {
      return true;
    }

    // Don't deal with selecting text
    if (utils.hasTextSelected(target)) {
      return true;
    }

    // Restrict number of digits
    const value = (target.value + digit).replace(/\D/g, '');
    const card = this.cardFromNumber(value);

    if (card) {
      if (value.length > card.length[card.length.length - 1]) {
        e.preventDefault();
        return false;
      }
    } else {
      if (value.length > 16) {
        e.preventDefault();
        return false;
      }
    }
  }

  /**
   * Given a card number, pattern match the card.
   *
   * @param  {string} number Card number from input
   * @return {Object}        Card
   */
  cardFromNumber(number) {
    const num = (number + '').replace(/\D/g, '');
    let card;

    for (card of constants.cards) {
      for (let pattern of card.patterns) {
        pattern = pattern + '';
        if (num.substr(0, pattern.length) === pattern) {
          return card;
        }
      }
    }
  }

  render() {
    const { disabled, error } = this.props;
    const value = this.state.value;
    const cardType = this.state.cardType;
    const fieldClasses = classnames('checkout-field', 'ccnumber', {error: error});

    return (
      <div className={fieldClasses}>
        <label className="checkout-field__label">CARD NUMBER</label>
        <input className="checkout-field__input"
          type="text"
          autoComplete="cc-number"
          ref="ccNumberInput"
          value={value}
          onKeyPress={(e) => this.onKeyPress(e)}
          onKeyDown={(e) => this.onKeyDown(e)}
          onKeyUp={(e) => this.onKeyUp(e)}
          onPaste={(e) => this.onPaste(e)}
          onChange={(e) => this.onChange(e)}
          onInput={(e) => this.onInput(e)}
          disabled={disabled}
        />
        {error ? (<div className="checkout-field__error">{error}</div>) : null}
      </div>
    );
  }
}

CCNumberInput.propTypes = {
  onStateUpdate: React.PropTypes.func,
  error: React.PropTypes.string,
  disabled: React.PropTypes.bool,
};

export default CCNumberInput;
