import PropTypes from 'prop-types';
import {
  forEachObjIndexed,
  keys,
  mapObjIndexed,
  omit,
  values,
} from 'ramda';
import React, { Component } from 'react';
import { List } from 'semantic-ui-react';

/* Interface */
const propTypes = {
  /** Whether to require a capital letter. */
  capital: PropTypes.bool,

  /** Minimum characters for password. */
  minCharacters: PropTypes.number,

  /** Minimum number of numeric characters. */
  minNumbers: PropTypes.number,

  /**
   * Callback to communicate validation state
   * @param {boolean} valid - Whether the password is valid.
   */
  onValidate: PropTypes.func,

  /** Password being tested. */
  password: PropTypes.string.isRequired,

  /** Whether special characters are required. */
  specialCharacters: PropTypes.bool,
};

/* Defaults */
const defaultProps = {
  capital: true,
  minCharacters: 8,
  minNumbers: 1,
  specialCharacters: false,
};

/**
 * Count the number of numbers in a string
 * @param {string} password - String to analyse.
 * @returns {number} total numeric characters in a string
 */
const countNumbers = (password) => {
  const numericCharacters = password.match(/[0-9]/g);
  return numericCharacters ? numericCharacters.length : 0;
};

export const validators = {
  minCharacters: {
    name: (minCharacters) => `Minimum ${minCharacters} characters`,
    validate: (password, minCharacters) => password.length >= minCharacters,
    isEnabled: (minCharacters) => minCharacters > 0,
  },
  minNumbers: {
    name: (minNumbers) =>
      `Minimum ${minNumbers} ${minNumbers > 1 ? 'numbers' : 'number'}`,
    validate:
      (password, minNumbers) => countNumbers(password) >= minNumbers,
    isEnabled: (minNumbers) => minNumbers > 0,
  },
  capital: {
    name: () => 'Contains a capital letter',
    validate: (password) => /[A-Z]/.test(password),
    isEnabled: (capital) => capital,
  },
  specialCharacters: {
    name: () => 'Must contain one of the special characters .!@#$%^&*()_+-=',
    validate: (password) => /\W|_/g.test(password),
    isEnabled: (specialCharacters) => specialCharacters,
  },
};

/**
 * Renders password conditions in a vertical list
 * @returns {Object} component
 */
class PasswordStrength extends Component {
  state = {
    valid: null,
  };

  componentDidMount = () => {
    this.validatePassword();
  };

  componentDidUpdate = () => {
    this.validatePassword();
  };

  validatePassword = () => {
    const { onValidate, password } = this.props;
    const { valid: previousValid } = this.state;

    if (!onValidate) {
      return;
    }

    let valid = true;

    forEachObjIndexed((validator, propName) => {
      const prop = this.props[propName];
      if (!validator.isEnabled(prop)) {
        return;
      }

      if (!validator.validate(password, prop)) {
        valid = false;
      }
    }, validators);

    if (valid !== previousValid) {
      onValidate(valid);
      this.setState({ valid });
    }
  };

  render = () => {
    const { password } = this.props;

    return (
      <List { ...omit(keys(propTypes), this.props) }>
        { values(mapObjIndexed((validator, propName) => {
          const propItem = this.props[propName];
          if (!validator.isEnabled(propItem)) {
            return null;
          }

          const checkValidation = validator.validate(password, propItem);

          return (
            <List.Item
              key={ propItem }
              style={ { color: checkValidation ? 'green' : 'red' } }
            >
              <List.Icon name={ checkValidation ? 'check' : 'x' } />
              <List.Content>{ validator.name(propItem) }</List.Content>
            </List.Item>
          );
        }, validators)) }
      </List>
    );
  }
}

PasswordStrength.propTypes = propTypes;
PasswordStrength.defaultProps = defaultProps;

export default PasswordStrength;
