'use strict';

var sprintf = require('sprintf-js').sprintf;

var v6 = require('./constants.js');

function groupPossibilities(possibilities) {
  return sprintf('(%s)', possibilities.join('|'));
}

function padGroup(group) {
  if (group.length < 4) {
    return sprintf('0{0,%d}%s', 4 - group.length, group);
  }

  return group;
}

var ADDRESS_BOUNDARY = '[^A-Fa-f0-9:]';

function simpleRegularExpression(groups) {
  var zeroIndexes = [];

  groups.forEach(function (group, i) {
    var groupInteger = parseInt(group, 16);

    if (groupInteger === 0) {
      zeroIndexes.push(i);
    }
  });

  // You can technically elide a single 0, this creates the regular expressions
  // to match that eventuality
  var possibilities = zeroIndexes.map(function (zeroIndex) {
    return groups.map(function (group, i) {
      if (i === zeroIndex) {
        var elision = (i === 0 || i === v6.GROUPS - 1) ? ':' : '';

        return groupPossibilities([padGroup(group), elision]);
      }

      return padGroup(group);
    }).join(':');
  });

  // The simplest case
  possibilities.push(groups.map(padGroup).join(':'));

  return groupPossibilities(possibilities);
}

function possibleElisions(elidedGroups, moreLeft, moreRight) {
  var left = moreLeft ? '' : ':';
  var right = moreRight ? '' : ':';

  var possibilities = [];

  // 1. elision of everything (::)
  if (!moreLeft && !moreRight) {
    possibilities.push('::');
  }

  // 2. complete elision of the middle
  if (moreLeft && moreRight) {
    possibilities.push('');
  }

  if ((moreRight && !moreLeft) || (!moreRight && moreLeft)) {
    // 3. complete elision of one side
    possibilities.push(':');
  }

  // 4. elision from the left side
  possibilities.push(sprintf('%s(:0{1,4}){1,%d}', left, elidedGroups - 1));

  // 5. elision from the right side
  possibilities.push(sprintf('(0{1,4}:){1,%d}%s', elidedGroups - 1, right));

  // 6. no elision
  possibilities.push(sprintf('(0{1,4}:){%d}0{1,4}', elidedGroups - 1));

  // 7. elision (including sloppy elision) from the middle
  for (var groups = 1; groups < elidedGroups - 1; groups++) {
    for (var position = 1; position < elidedGroups - groups; position++) {
      possibilities.push(sprintf('(0{1,4}:){%d}:(0{1,4}:){%d}0{1,4}',
        position,
        elidedGroups - position - groups - 1));
    }
  }

  return groupPossibilities(possibilities);
}

/**
 * Generate a regular expression string that can be used to find or validate
 * all variations of this address
 * @memberof Address6
 * @instance
 * @param {string} optionalSubString
 * @returns {string}
 */
exports.regularExpressionString = function (optionalSubString) {
  if (optionalSubString === undefined) {
    optionalSubString = false;
  }

  var output = [];

  // TODO: revisit why this is necessary
  var address6 = new this.constructor(this.correctForm());

  if (address6.elidedGroups === 0) {
    // The simple case
    output.push(simpleRegularExpression(address6.parsedAddress));
  } else if (address6.elidedGroups === v6.GROUPS) {
    // A completely elided address
    output.push(possibleElisions(v6.GROUPS));
  } else {
    // A partially elided address
    var halves = address6.address.split('::');

    if (halves[0].length) {
      output.push(simpleRegularExpression(halves[0].split(':')));
    }

    output.push(possibleElisions(address6.elidedGroups,
      halves[0].length !== 0,
      halves[1].length !== 0));

    if (halves[1].length) {
      output.push(simpleRegularExpression(halves[1].split(':')));
    }

    output = [output.join(':')];
  }

  if (!optionalSubString) {
    output = [].concat(
      '(?=^|',
      ADDRESS_BOUNDARY,
      '|[^\\w\\:])(', output, ')(?=[^\\w\\:]|',
      ADDRESS_BOUNDARY,
      '|$)');
  }

  return output.join('');
};

/**
 * Generate a regular expression that can be used to find or validate all
 * variations of this address.
 * @memberof Address6
 * @instance
 * @param {string} optionalSubString
 * @returns {RegExp}
 */
exports.regularExpression = function (optionalSubstring) {
  return new RegExp(this.regularExpressionString(optionalSubstring), 'i');
};
