var nodeSemver = require('semver');
var sver = require('./sver');
var Semver = sver.Semver;
var SemverRange = sver.SemverRange;
var forOf = require('es6-iterator/for-of');

module.exports = function nodeRangeToSemverRange (range) {
  var parsed = nodeSemver.validRange(range);

  // tag version
  if (!parsed)
    return new SemverRange(range);

  if (parsed === '*')
    return new SemverRange(parsed);

  try {
    var semverRange = new SemverRange(range);
    if (!semverRange.version.tag)
      return semverRange;
  }
  catch (e) {
    if (e.code !== 'ENOTSEMVER')
      throw e;
  }

  var outRange;
  forOf(parsed.split('||'), function(union) {

    // compute the intersection into a lowest upper bound and a highest lower bound
    var upperBound, lowerBound, upperEq, lowerEq;
    forOf(union.split(' '), function(intersection, doBreak) {
      var lt = intersection[0] === '<';
      var gt = intersection[0] === '>';
      if (!lt && !gt) {
        upperBound = intersection;
        upperEq = true;
        return doBreak();
      }
      var eq = intersection[1] === '=';
      if (!gt) {
        var version = new Semver(intersection.substr(1 + eq));
        if (!upperBound || upperBound.gt(version)) {
          upperBound = version;
          upperEq = eq;
        }
      }
      else if (!lt) {
        var eq = intersection[1] === '=';
        var version = new Semver(intersection.substr(1 + eq));
        if (!lowerBound || lowerBound.lt(version)) {
          lowerBound = version;
          lowerEq = eq;
        }
      }
    });

    // if the lower bound is greater than the upper bound then just return the lower bound exactly
    if (lowerBound && upperBound && lowerBound.gt(upperBound)) {
      var curRange = new SemverRange(lowerBound.toString());
      // the largest or highest union range wins
      if (!outRange || !outRange.contains(curRange) && (curRange.gt(outRange) || curRange.contains(outRange)))
        outRange = curRange;
      return;
    }

    // determine the largest semver range satisfying the upper bound
    var upperRange;
    if (upperBound) {
      // if the upper bound has an equality then we return it directly
      if (upperEq) {
        var curRange = new SemverRange(upperBound.toString());
        // the largest or highest union range wins
        if (!outRange || !outRange.contains(curRange) && (curRange.gt(outRange) || curRange.contains(outRange)))
          outRange = curRange;
        return;
      }

      // prerelease ignored in upper bound
      var major = 0, minor = 0, patch = 0, rangeType = '';

      // <2.0.0 -> ^1.0.0
      if (upperBound.patch === 0) {
        if (upperBound.minor === 0) {
          if (upperBound.major > 0) {
            major = upperBound.major - 1;
            rangeType = '^';
          }
        }
        // <1.2.0 -> ~1.1.0
        else {
          major = upperBound.major;
          minor = upperBound.minor - 1;
          rangeType = '~';
        }
      }
      // <1.2.3 -> ~1.2.0
      else {
        major = upperBound.major;
        minor = upperBound.minor;
        patch = 0;
        rangeType = '~';
      }

      if (major === 0 && rangeType === '^')
        upperRange = new SemverRange('0');
      else
        upperRange = new SemverRange(rangeType + major + '.' + minor + '.' + patch);
    }

    // determine the lower range semver range
    var lowerRange;
    if (!lowerEq) {
      if (lowerBound.pre)
        lowerRange = new SemverRange('^' + lowerBound.major + '.' + lowerBound.minor + '.' + lowerBound.patch + '-' + lowerBound.pre.join('.') + '.1');
      else
        lowerRange = new SemverRange('^' + lowerBound.major + '.' + lowerBound.minor + '.' + (lowerBound.patch + 1));
    }
    else {
      lowerRange = new SemverRange('^' + lowerBound.toString());
    }

    // we then intersect the upper semver range with the lower semver range
    // if the intersection is empty, we return the upper range only
    var curRange = upperRange ? lowerRange.intersect(upperRange) || upperRange : lowerRange;

    // the largest or highest union range wins
    if (!outRange || !outRange.contains(curRange) && (curRange.gt(outRange) || curRange.contains(outRange)))
      outRange = curRange;
  });
  return outRange;
}