app = angular.module "plunker", [
  "ui.bootstrap"
  "ui.compat"
  "plunker.catalogue"
]

app.directive "plunkerPackageBlock", [ () ->
  restrict: "E"
  replace: true
  scope:
    'package': "="
  template: """
    <div class="plunker-package-block">
      <div class="package-header">

        <h4>
          <ul class="package-meta inline pull-right">
            <li><i class="icon-download"></i><span ng-bind="package.bumps"></span></li>
          </ul>
          <a ng-href="packages/{{package.name}}" ng-bind="package.name"></a>
          <ul class="package-versions inline">
            <li ng-repeat="version in package.versions | limitTo:3">
              <a class="label" ng-href="packages/{{package.name}}/versions/{{version.semver}}" ng-bind="version.semver"></a>
            </li>
            <li class="dropdown" ng-show="package.versions.length > 3">
              <a class="more dropdown-toggle" ng-href="spackages/{{package.name}}/versions">More...</a>
              <ul class="dropdown-menu">
                <li ng-repeat="version in package.versions | limitTo:3 - package.versions.length">
                  <a ng-href="packages/{{package.name}}/versions/{{version.semver}}" ng-bind="version.semver"></a>
                </li>
              </ul>
            </li>
          </ul>
        </h4>
      </div>
      <p class="package-description" ng-bind="package.description"></p>

    </div>
  """
  link: ($scope, $el, attrs) ->
    
]

app.config ["$stateProvider", "$routeProvider", ($stateProvider, $routeProvider) ->
  $stateProvider.state "catalogue",
    abstract: true
    url: ""
    template: """
      <div class="plunker-catalogue">
        <form class="plunker-searchbar" ng-submit="search()">
          <a class="btn pull-left" href=""><i class="icon-home"></i></a>
          <div class="plunker-searchbox input-append">
            <div class="input-wrapper">
              <input class="" type="text" ng-model="query" placeholder="Search packages..." />
            </div>
            <button class="btn"><i class="icon-search"></i></button>
          </div>
        </form>
        <div ui-view></div>
      </div>
    """
    controller: ["$scope", "$state", "catalogue", ($scope, $state, catalogue) ->
      $scope.search = (query) ->
        $state.transitionTo("catalogue.search", query: $scope.query)
      
    ]

  $stateProvider.state "catalogue.popular",
    url: "/catalogue/popular"
    template: """
      <div>
        <div class="sub-header">
          <h3>
            <small class="pull-right"><a href="packages/create">Add new...</a></small>
            Popular packages
          </h3>
        </div>
        <plunker-package-block package="package" ng-repeat="package in popular"></plunker-package-block>
      </div>
    """
    controller: ["$scope", "$state", "catalogue", ($scope, $state, catalogue) ->      
      $scope.popular = catalogue.findAll()
    ]

  $stateProvider.state "catalogue.search",
    url: "/catalogue/search/:query"
    template: """
      <div>
        <div class="sub-header">
          <h3>Search results: <span ng-bind="search"></span></h3>
        </div>
        <plunker-package-block package="package" ng-repeat="package in results"></plunker-package-block>
        <p ng-hide="!results || results.length">No results found for {{search}}.</p>
        <p ng-hide="results && !results.loading">Searching for {{search}}.</p>
      </div>
    """
    controller: ["$scope", "$state", "$stateParams", "catalogue", ($scope, $state, $stateParams, catalogue) ->
      $scope.search = $stateParams.query
      $scope.results = catalogue.search($stateParams.query)
    ]

  $stateProvider.state "catalogue.create",
    url: "/packages/create"
    templateUrl: "partials/package/edit.html"
    controller: ["$scope", "$state", "$stateParams", "catalogue", ($scope, $state, $stateParams, catalogue) ->
      $scope.prefix = "Create new package"
      $scope.editing = {}
      
      $scope.cancel = -> $state.transitionTo("catalogue.popular")
      $scope.save = ->
        catalogue.create($scope.editing).then (pkg) ->
          console.log "Created package", pkg
    ]    


  $stateProvider.state "catalogue.package",
    url: "/packages/:name"
    template: """
      <div ui-view>
      </div>
    """
    resolve:
      pkg: ["$stateParams", "catalogue", ($stateParams, catalogue) ->
        catalogue.findOne(name: $stateParams.name)
      ]
    controller: ["$scope", "$state", "$stateParams", "pkg", ($scope, $state, $stateParams, pkg) ->
      console.log "Setting package to", pkg
      $scope.package = pkg
      
      unless pkg.versionCount or pkg.versions?.length
        return $state.transitionTo("catalogue.package.createVersion", name: pkg.name)
        
      #$scope.currentVersion = null
      if $state.is("catalogue.package")
        return $state.transitionTo("catalogue.package.version.detail", name: pkg.name, semver: pkg.getMatchingVersion().semver) unless $scope.currentVersion
    ]

  $stateProvider.state "catalogue.package.createVersion",
    url: "/versions/create"
    template: """
      <div class="sub-header">
        <h3>{{prefix}}<a ng-href="packages/{{package.name}}/edit" ng-bind="package.name"></a></h3>
      </div>
      <version-editor save="save(editing)" editing="editing" package="package"></version-editor>
    """
    controller: ["$scope", "$state", "$stateParams", "pkg", ($scope, $state, $stateParams, pkg) ->
      $scope.package = pkg
      $scope.prefix = "Adding version: "
      $scope.editing =
        scripts: []
        styles: []
        dependencies: []
        
      $scope.save = ->
        $scope.package.addVersion($scope.editing).then ->
          $state.transitionTo("catalogue.package.version.detail", name: $scope.package.name, semver: $scope.editing.semver)
      
    ]
    
  $stateProvider.state "catalogue.package.version",
    abstract: true
    url: "/versions/:semver"
    template: """
      <div>
        <div class="sub-header">
          <div class="package-version-toggle dropdown pull-right">
            <div class="dropdown-toggle"><span ng-bind="currentVersion.semver"></span><span class="caret"></span></div>
            <ul class="dropdown-menu">
              <li ng-class="{active: version == currentVersion}" ng-repeat="version in package.versions">
                <a ng-click="transitionTo(package, version)" ng-bind="version.semver"></a>
              </li>
            </ul>
          </div>
          <h3>{{prefix}}<a ng-href="packages/{{package.name}}/edit" ng-bind="package.name"></a></h3>
        </div>
        <div ui-view></div>
      </div>
    """
    controller: ["$scope", "$state", "$stateParams", "pkg", ($scope, $state, $stateParams, pkg) ->
      $scope.package = pkg
      $scope.currentVersion = $scope.package.getMatchingVersion($stateParams.semver)
      $scope.transitionTo = (pkg, version) ->
        $state.transitionTo($state.current.name, name: pkg.name, semver: version.semver)
    ]    

  $stateProvider.state "catalogue.package.version.detail",
    url: ""
    template: """
      <p ng-bind="package.description"></p>
      <ul class="package-meta inline">
        <li ng-show="package.homepage"><i class="icon-link"></i><a ng-href="{{package.homepage}}">Website</a></li>
        <li ng-show="package.documentation"><i class="icon-info-sign"></i><a ng-href="{{package.documentation}}">Docs</a></li>
      </ul>
      <details class="package-scripts" ng-show="currentVersion.scripts.length">
        <summary>Scripts <span ng-bind-template="({{currentVersion.scripts.length}})"></span></summary>
        <ol>
          <li ng-repeat="url in currentVersion.scripts"><code ng-bind="url"></code></li>
        </ol>
      </details>
      <details class="package-scripts" ng-show="currentVersion.styles.length">
        <summary>Stylesheets <span ng-bind-template="({{currentVersion.styles.length}})"></span></summary>
        <ol>
          <li ng-repeat="url in currentVersion.styles"><code ng-bind="url"></code></li>
        </ol>
      </details>
      <details class="package-scripts" ng-show="currentVersion.dependencies.length">
        <summary>Stylesheets <span ng-bind-template="({{currentVersion.dependencies.length}})"></span></summary>
        <ol>
          <li ng-repeat="dependency in currentVersion.dependencies"><strong ng-bind="dependency.name"></strong> - <code ng-bind="dependency.semver"></code></li>
        </ol>
      </details>
      <ul class="package-ops inline pull-right">
        <li>
          <button class="btn btn-small" tooltip="Add the selected package and its dependencies to your active plunk">
            <i class="icon-magic"></i>
            Add
          </button>
        </li>
        <li>
          <a class="btn btn-small btn-primary" ng-href="packages/{{package.name}}/edit" tooltip="Edit this package">
            <i class="icon-pencil"></i>
            Edit
          </a>
        </li>
      </ul>
    """
    controller: ["$scope", "$state", "$stateParams", ($scope, $state, $stateParams) ->
      console.log "$stateParams", $stateParams
    ]    

  $stateProvider.state "catalogue.package.edit",
    url: "/edit"
    templateUrl: "partials/package/edit.html"
    controller: ["$scope", "$state", "$stateParams", ($scope, $state, $stateParams) ->
      $scope.prefix = "Edit: "
      $scope.currentVersion = $scope.package.getMatchingVersion($stateParams.semver)
      $scope.editing = angular.copy($scope.package)
      
      $scope.cancel = ->
        $state.transitionTo("catalogue.package.version.detail", $scope.package)

      $scope.save = ->
        $scope.package.save($scope.editing).then (pkg) ->
          console.log "Package saved", pkg
          $state.transitionTo("catalogue.package.version.detail", $scope.package)
    ]    

  $stateProvider.state "catalogue.package.version.edit",
    url: "/edit"
    template: """
      <version-editor save="save(editing)" editing="editing"></version-editor>
    """
    controller: ["$scope", "$state", "$stateParams", ($scope, $state, $stateParams) ->
      $scope.prefix = "Edit: "
      $scope.currentVersion = $scope.package.getMatchingVersion($stateParams.semver)
      $scope.editing = angular.copy($scope.currentVersion)
      
    ]    
 
]

app.config ["$locationProvider", "$routeProvider", ($locationProvider, $routeProvider) ->
  $locationProvider.html5Mode(true)
  $routeProvider.when "/",
    controller: ["$state", ($state) -> 
      $state.transitionTo("catalogue.popular")
    ]
]



app.run ["$templateCache", ($templateCache) ->
  $templateCache.put "partials/package/edit.html", """
    <div class="sub-header">
      <h3>{{prefix}}<a ng-href="packages/{{package.name}}/edit" ng-bind="package.name"></a></h3>
    </div>
    <form class="package-editor" ng-submit="save()">
      <label>Package name:
        <input ng-disabled="package.name" ng-model="editing.name" placeholder="package_name" required>
      </label>
      <label>Description:
        <textarea ng-model="editing.description" placeholder="Package description..."></textarea>
      </label>
      <label>Website:
        <input ng-model="editing.homepage" placeholder="http://homepage">
      </label>
      <label>Documentation:
        <input ng-model="editing.documentation" placeholder="http://documentation">
      </label>
      <div ng-show="package.name">
        <label>Versions:</label>
        <ul class="" ng-show="package.name">
          <li ng-repeat="version in editing.versions">
            <a ng-href="packages/{{package.name}}/versions/{{version.semver}}/edit" ng-bind="version.semver"></a>
          </li>
          <li class="add-new">
            <a ng-href="packages/{{package.name}}/versions/create">Add new...</a>
          </li>
        </ul>
      </div>
      <div>
        <button class="btn btn-primary">Save</button>
        <button class="btn" type="button" ng-click="cancel()">Cancel</button>
      </div>
    </form>
  """
]


app.directive "versionEditor", [ () ->
  restrict: "E"
  replace: true
  scope:
    editing: "="
    package: "="
    save: "&"
  template: """
    <form name="versionEditor" class="version-editor" ng-submit="save()">
      <label>Semver:
        <input ng-model="editing.semver" placeholder="0.0.1" semver required>
      </label>
      <label>Scripts:</label>
      <ul class="">
        <li ng-repeat="url in editing.scripts">
          <ul class="inline pull-right">
            <li><a ng-disabled="$first" ng-click="moveUp(editing.scripts, $index)"><i class="icon-arrow-up"></i></a></li>
            <li><a ng-disabled="$last" ng-click="moveDown(editing.scripts, $index)"><i class="icon-arrow-down"></i></a></li>
            <li><a ng-click="remove('script', editing.scripts, $index)"><i class="icon-trash"></i></a></li>
          </ul>
          <a ng-click="editUrl('script', editing.scripts, $index)" ng-bind="url"></a>
        </li>
        <li class="add-new">
          <a ng-click="addNewElement('script', editing.scripts)">Add new...</a>
        </li>
      </ul>
      <label>Styles:</label>
      <ul class="">
        <li ng-repeat="url in editing.styles">
          <ul class="inline pull-right">
            <li><a ng-disabled="$first" ng-click="moveUp(editing.styles, $index)"><i class="icon-arrow-up"></i></a></li>
            <li><a ng-disabled="$last" ng-click="moveDown(editing.styles, $index)"><i class="icon-arrow-down"></i></a></li>
            <li><a ng-click="remove('script', editing.styles, $index)"><i class="icon-trash"></i></a></li>
          </ul>
          <a ng-click="editUrl('script', editing.styles, $index)" ng-bind="url"></a>
        </li>
        <li class="add-new">
          <a ng-click="addNewElement('script', editing.styles)">Add new...</a>
        </li>
      </ul>
      <label>Dependencies:</label>
      <ul class="">
        <li ng-repeat="dep in editing.dependencies">
          <ul class="inline pull-right">
            <li><a ng-disabled="$first" ng-click="moveUp(editing.dependencies, $index)"><i class="icon-arrow-up"></i></a></li>
            <li><a ng-disabled="$last" ng-click="moveDown(editing.dependencies, $index)"><i class="icon-arrow-down"></i></a></li>
            <li><a ng-click="remove('dependency', editing.dependencies, $index)"><i class="icon-trash"></i></a></li>
          </ul>
          <a ng-click="editDependency('dependency', editing.dependencies, $index)" ng-bind="dep"></a>
        </li>
        <li class="add-new">
          <a ng-click="addNewElement('dependency', editing.dependencies)">Add new...</a>
        </li>
      </ul>
      <div>
        <button ng-disabled="!versionEditor.$valid" class="btn btn-primary">Save</button>
        <a class="btn" ng-href="packages/{{package.name}}/edit">Cancel</a>
      </div>
    </form>
  """
  link: ($scope, $el, attrs) ->
    $scope.editUrl = (type, list, index) ->
      value = prompt("Enter the updated #{type}:", list[index])
      
      if value then list[index] = value
    
    $scope.addNewElement = (type, list) ->
      value = prompt("Enter the new #{type}:")
      if value then list.push(value)
      
    $scope.moveUp = (list, index) ->
      if index
        prev = index - 1
        [list[prev], list[index]] = [list[index], list[prev]]
      
    $scope.moveDown = (list, index) ->
      if index < list.length - 1
        next = index + 1
        [list[next], list[index]] = [list[index], list[next]]
    
    $scope.remove = (type, list, index) ->
      if confirm("Are you sure that you would like to remove the #{type} #{list[index]}?")
        list.splice(index, 1)
]

app.directive "loading", [ () ->
  
]


app.run ["$rootScope", ($rootScope) ->
]

<!DOCTYPE html>
<html ng-app="plunker">
  
  <head>
    <meta charset="utf-8">
    <title>AngularJS Plunker</title>
    <script>
      document.write('<base href="' + document.location + '" />');
    </script>
    <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
    <link href="//gregoryloucas.github.io/Fontstrap/assets/css/font-awesome.css" rel="stylesheet">
    <link rel="stylesheet" href="style.css">
    <script src="http://code.angularjs.org/1.1.4/angular.js"></script>
    <script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.3.0.min.js" type="text/javascript" charset="utf-8"></script>
    <script src="http://angular-ui.github.io/ui-router/build/angular-ui-states.js"></script>
    <script src="semver.js"></script>
    <script src="app.js"></script>
    <script src="catalogue.js"></script>
  </head>
  
  <body class="container" ui-view></body>

</html>
/* Put your css in here */
.plunker-searchbar {
  background-color: #ccc;
  margin: 0 -20px;
  padding: 4px 20px;
}

h3 {
  font-size: 16px;
  line-height: 20px;
  border-bottom: 1px solid #999;
}

details {
  overflow-x: hidden;
}

.plunker-searchbox {
  display: block;
  position: relative;
  height: 30px;
  margin: 0;
  margin-left: 44px;
  
  button {
    width: 40px;
    position: absolute;
    right: 0px;
  }
  .input-wrapper {
    position: absolute;
    display: block;
    right: 39px;
    left: 0;
    top: 0;
    bottom: 0;
    
    input {
      width: 100%;
      height: 100%;
    -webkit-box-sizing: border-box;
       -moz-box-sizing: border-box;
            box-sizing: border-box;
    }
  }
}

.plunker-package-block {
  h4 {
    margin: 4px 0;
    font-size: 14px;
  }
  .package-description {
    font-size: 12px;
  }
  .package-versions {
    display: inline-block;
    margin-bottom: 0;
    
    .label, .more {
      font-size: 9px;
    }
  }
  .package-meta {
    font-size: 11px;
  }
}

.package-editor {
  label>textarea, label>input,.in-place>input {
    width: 100%;
    display: block;
    -webkit-box-sizing: border-box;
       -moz-box-sizing: border-box;
            box-sizing: border-box;
  }

}
.ng-invalid {
  border-color: red;
}
module = angular.module "plunker.catalogue", []

module.factory "catalogue", ["$http", ($http) ->
  apiUrl = "http://api.plnkr.co"
  
  identityMap = {}
  
  class Package
    constructor: (data = {}, options = {}) ->
      if data.name and instance = identityMap[data.name]
        instance.update(data)
        
        return instance
      
      pkg = @
      
      angular.copy(data, pkg)
      
      if options.serverState
        pkg.$serverState = angular.copy(data)
        identityMap[data.name] = pkg
      else unless pkg.$serverState
        pkg.then = -> pkg.refresh().then
    
    update: (data = {}) ->
      pkg = @
      
      angular.extend pkg, data
      
      pkg
    
    save: (data = {}, options = {}) ->
      pkg = @
      
      throw new Error("Attempting to save a package without a primary key") unless pkg.name
      
      options.cache = false
      
      payload =
        description: data.description
        homepage: data.homepage
        documentation: data.documentation
        
      request = $http.post("#{apiUrl}/catalogue/packages/#{pkg.name}", payload, options).then (response) ->
        pkg.update(response.data)
        
        delete pkg.then
        
        pkg
      
      pkg.then = request.then.bind(request)
      pkg
    
    addVersion: (data = {}, options = {}) ->
      pkg = @
      
      throw new Error("Attempting to add a version without a primary key") unless pkg.name
      
      options.cache = false
      
      payload = {}
      payload.scripts = angular.copy(data.scripts) if data.scripts.length
      payload.styles = angular.copy(data.styles) if data.styles.length
        
      request = $http.post("#{apiUrl}/catalogue/packages/#{pkg.name}/versions", payload, options).then (response) ->
        pkg.update(response.data)
        
        delete pkg.then
        
        pkg
      
      pkg.then = request.then.bind(request)
      pkg
    
    bump: (options = {}) ->
      pkg = @
      
      throw new Error("Attempting to refresh a package without a primary key") unless pkg.name
      
      options.cache = false
        
      request = $http.post("#{apiUrl}/catalogue/packages/#{pkg.name}/bump", {}, options).then (response) ->
        pkg.update(response.data)
        
        delete pkg.then
        
        pkg
      
      pkg.then = request.then.bind(request)
      pkg
          
    refresh: (options = {})->
      pkg = @
      
      throw new Error("Attempting to refresh a package without a primary key") unless pkg.name
      
      options.cache = false
        
      request = $http.get("#{apiUrl}/catalogue/packages/#{pkg.name}", options).then (response) ->
        pkg.update(response.data)
        
        delete pkg.then
        
        pkg
      
      pkg.then = request.then.bind(request)
      pkg
    
    # TODO: This does nothing useful
    getMatchingVersion: (range = "*") ->
      bestMatch = null
      
      for version in @versions when semver.satisfies(version.semver, range) and (not bestMatch or semver.gt(version.semver, bestMatch.semver))
        bestMatch = version
      
      bestMatch
      
  # Coffee-Script will implicitly return the object below
  
  # Return an array of trending plunks
  findOne: (data = {}) -> new Package(data)
  search: (query) -> @findAll(params: query: query)
  findAll: (options = {}) ->
    options.cache = false
    options.url ||= "#{apiUrl}/catalogue/packages"

    packages = []
    packages.loading = true
    packages.refresh = ->
      request = $http.get(options.url, options).then (response) ->
        packages.length = 0
        packages.push(new Package(json, serverState: true)) for json in response.data
        
        delete packages.loading
        delete packages.then
        
        packages
      
      packages.then = request.then.bind(request)
      packages
    
    packages.then = -> packages.refresh().then
    packages
  create: (data, options = {}) ->
    options.cache ?= true
    options.url ||= "#{apiUrl}/catalogue/packages"
    
    request = $http.post(options.url, data, options).then (response) ->
      new Package(response.data, $serverState: response.data)
    , (err) -> new Error("Failed to create package" + err.message)
    
]

module.config ["$httpProvider", ($httpProvider) ->
  $httpProvider.defaults.headers.common['Authorization'] = "token 51782caaa65dee000000000a"
]
;(function (exports) { // nothing in here is node-specific.

// See http://semver.org/
// This implementation is a *hair* less strict in that it allows
// v1.2.3 things, and also tags that don't begin with a char.

var semver = "\\s*[v=]*\\s*([0-9]+)"        // major
           + "\\.([0-9]+)"                  // minor
           + "\\.([0-9]+)"                  // patch
           + "(-[0-9]+-?)?"                 // build
           + "([a-zA-Z-+][a-zA-Z0-9-\.:]*)?" // tag
  , exprComparator = "^((<|>)?=?)\s*("+semver+")$|^$"
  , xRangePlain = "[v=]*([0-9]+|x|X|\\*)"
                + "(?:\\.([0-9]+|x|X|\\*)"
                + "(?:\\.([0-9]+|x|X|\\*)"
                + "([a-zA-Z-][a-zA-Z0-9-\.:]*)?)?)?"
  , xRange = "((?:<|>)=?)?\\s*" + xRangePlain
  , exprLoneSpermy = "(?:~>?)"
  , exprSpermy = exprLoneSpermy + xRange
  , expressions = exports.expressions =
    { parse : new RegExp("^\\s*"+semver+"\\s*$")
    , parsePackage : new RegExp("^\\s*([^\/]+)[-@](" +semver+")\\s*$")
    , parseRange : new RegExp(
        "^\\s*(" + semver + ")\\s+-\\s+(" + semver + ")\\s*$")
    , validComparator : new RegExp("^"+exprComparator+"$")
    , parseXRange : new RegExp("^"+xRange+"$")
    , parseSpermy : new RegExp("^"+exprSpermy+"$")
    }


Object.getOwnPropertyNames(expressions).forEach(function (i) {
  exports[i] = function (str) {
    return ("" + (str || "")).match(expressions[i])
  }
})

exports.rangeReplace = ">=$1 <=$7"
exports.clean = clean
exports.compare = compare
exports.rcompare = rcompare
exports.satisfies = satisfies
exports.gt = gt
exports.gte = gte
exports.lt = lt
exports.lte = lte
exports.eq = eq
exports.neq = neq
exports.cmp = cmp
exports.inc = inc

exports.valid = valid
exports.validPackage = validPackage
exports.validRange = validRange
exports.maxSatisfying = maxSatisfying

exports.replaceStars = replaceStars
exports.toComparators = toComparators

function stringify (version) {
  var v = version
  return [v[1]||'', v[2]||'', v[3]||''].join(".") + (v[4]||'') + (v[5]||'')
}

function clean (version) {
  version = exports.parse(version)
  if (!version) return version
  return stringify(version)
}

function valid (version) {
  if (typeof version !== "string") return null
  return exports.parse(version) && version.trim().replace(/^[v=]+/, '')
}

function validPackage (version) {
  if (typeof version !== "string") return null
  return version.match(expressions.parsePackage) && version.trim()
}

// range can be one of:
// "1.0.3 - 2.0.0" range, inclusive, like ">=1.0.3 <=2.0.0"
// ">1.0.2" like 1.0.3 - 9999.9999.9999
// ">=1.0.2" like 1.0.2 - 9999.9999.9999
// "<2.0.0" like 0.0.0 - 1.9999.9999
// ">1.0.2 <2.0.0" like 1.0.3 - 1.9999.9999
var starExpression = /(<|>)?=?\s*\*/g
  , starReplace = ""
  , compTrimExpression = new RegExp("((<|>)?=|<|>)\\s*("
                                    +semver+"|"+xRangePlain+")", "g")
  , compTrimReplace = "$1$3"

function toComparators (range) {
  var ret = (range || "").trim()
    .replace(expressions.parseRange, exports.rangeReplace)
    .replace(compTrimExpression, compTrimReplace)
    .split(/\s+/)
    .join(" ")
    .split("||")
    .map(function (orchunk) {
      return orchunk
        .replace(new RegExp("(" + exprLoneSpermy + ")\\s+"), "$1")
        .split(" ")
        .map(replaceXRanges)
        .map(replaceSpermies)
        .map(replaceStars)
        .join(" ").trim()
    })
    .map(function (orchunk) {
      return orchunk
        .trim()
        .split(/\s+/)
        .filter(function (c) { return c.match(expressions.validComparator) })
    })
    .filter(function (c) { return c.length })
  return ret
}

function replaceStars (stars) {
  return stars.trim().replace(starExpression, starReplace)
}

// "2.x","2.x.x" --> ">=2.0.0- <2.1.0-"
// "2.3.x" --> ">=2.3.0- <2.4.0-"
function replaceXRanges (ranges) {
  return ranges.split(/\s+/)
               .map(replaceXRange)
               .join(" ")
}

function replaceXRange (version) {
  return version.trim().replace(expressions.parseXRange,
                                function (v, gtlt, M, m, p, t) {
    var anyX = !M || M.toLowerCase() === "x" || M === "*"
               || !m || m.toLowerCase() === "x" || m === "*"
               || !p || p.toLowerCase() === "x" || p === "*"
      , ret = v

    if (gtlt && anyX) {
      // just replace x'es with zeroes
      ;(!M || M === "*" || M.toLowerCase() === "x") && (M = 0)
      ;(!m || m === "*" || m.toLowerCase() === "x") && (m = 0)
      ;(!p || p === "*" || p.toLowerCase() === "x") && (p = 0)
      ret = gtlt + M+"."+m+"."+p+"-"
    } else if (!M || M === "*" || M.toLowerCase() === "x") {
      ret = "*" // allow any
    } else if (!m || m === "*" || m.toLowerCase() === "x") {
      // append "-" onto the version, otherwise
      // "1.x.x" matches "2.0.0beta", since the tag
      // *lowers* the version value
      ret = ">="+M+".0.0- <"+(+M+1)+".0.0-"
    } else if (!p || p === "*" || p.toLowerCase() === "x") {
      ret = ">="+M+"."+m+".0- <"+M+"."+(+m+1)+".0-"
    }
    return ret
  })
}

// ~, ~> --> * (any, kinda silly)
// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0
// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0
// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0
// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0
// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0
function replaceSpermies (version) {
  return version.trim().replace(expressions.parseSpermy,
                                function (v, gtlt, M, m, p, t) {
    if (gtlt) throw new Error(
      "Using '"+gtlt+"' with ~ makes no sense. Don't do it.")

    if (!M || M.toLowerCase() === "x") {
      return ""
    }
    // ~1 == >=1.0.0- <2.0.0-
    if (!m || m.toLowerCase() === "x") {
      return ">="+M+".0.0- <"+(+M+1)+".0.0-"
    }
    // ~1.2 == >=1.2.0- <1.3.0-
    if (!p || p.toLowerCase() === "x") {
      return ">="+M+"."+m+".0- <"+M+"."+(+m+1)+".0-"
    }
    // ~1.2.3 == >=1.2.3- <1.3.0-
    t = t || "-"
    return ">="+M+"."+m+"."+p+t+" <"+M+"."+(+m+1)+".0-"
  })
}

function validRange (range) {
  range = replaceStars(range)
  var c = toComparators(range)
  return (c.length === 0)
       ? null
       : c.map(function (c) { return c.join(" ") }).join("||")
}

// returns the highest satisfying version in the list, or null
function maxSatisfying (versions, range) {
  return versions
    .filter(function (v) { return satisfies(v, range) })
    .sort(compare)
    .pop() || null
}
function satisfies (version, range) {
  version = valid(version)
  if (!version) return false
  range = toComparators(range)
  for (var i = 0, l = range.length ; i < l ; i ++) {
    var ok = false
    for (var j = 0, ll = range[i].length ; j < ll ; j ++) {
      var r = range[i][j]
        , gtlt = r.charAt(0) === ">" ? gt
               : r.charAt(0) === "<" ? lt
               : false
        , eq = r.charAt(!!gtlt) === "="
        , sub = (!!eq) + (!!gtlt)
      if (!gtlt) eq = true
      r = r.substr(sub)
      r = (r === "") ? r : valid(r)
      ok = (r === "") || (eq && r === version) || (gtlt && gtlt(version, r))
      if (!ok) break
    }
    if (ok) return true
  }
  return false
}

// return v1 > v2 ? 1 : -1
function compare (v1, v2) {
  var g = gt(v1, v2)
  return g === null ? 0 : g ? 1 : -1
}

function rcompare (v1, v2) {
  return compare(v2, v1)
}

function lt (v1, v2) { return gt(v2, v1) }
function gte (v1, v2) { return !lt(v1, v2) }
function lte (v1, v2) { return !gt(v1, v2) }
function eq (v1, v2) { return gt(v1, v2) === null }
function neq (v1, v2) { return gt(v1, v2) !== null }
function cmp (v1, c, v2) {
  switch (c) {
    case ">": return gt(v1, v2)
    case "<": return lt(v1, v2)
    case ">=": return gte(v1, v2)
    case "<=": return lte(v1, v2)
    case "==": return eq(v1, v2)
    case "!=": return neq(v1, v2)
    case "===": return v1 === v2
    case "!==": return v1 !== v2
    default: throw new Error("Y U NO USE VALID COMPARATOR!? "+c)
  }
}

// return v1 > v2
function num (v) {
  return v === undefined ? -1 : parseInt((v||"0").replace(/[^0-9]+/g, ''), 10)
}
function gt (v1, v2) {
  v1 = exports.parse(v1)
  v2 = exports.parse(v2)
  if (!v1 || !v2) return false

  for (var i = 1; i < 5; i ++) {
    v1[i] = num(v1[i])
    v2[i] = num(v2[i])
    if (v1[i] > v2[i]) return true
    else if (v1[i] !== v2[i]) return false
  }
  // no tag is > than any tag, or use lexicographical order.
  var tag1 = v1[5] || ""
    , tag2 = v2[5] || ""

  // kludge: null means they were equal.  falsey, and detectable.
  // embarrassingly overclever, though, I know.
  return tag1 === tag2 ? null
         : !tag1 ? true
         : !tag2 ? false
         : tag1 > tag2
}

function inc (version, release) {
  version = exports.parse(version)
  if (!version) return null

  var parsedIndexLookup =
    { 'major': 1
    , 'minor': 2
    , 'patch': 3
    , 'build': 4 }
  var incIndex = parsedIndexLookup[release]
  if (incIndex === undefined) return null

  var current = num(version[incIndex])
  version[incIndex] = current === -1 ? 1 : current + 1

  for (var i = incIndex + 1; i < 5; i ++) {
    if (num(version[i]) !== -1) version[i] = "0"
  }

  if (version[4]) version[4] = "-" + version[4]
  version[5] = ""

  return stringify(version)
}
})(typeof exports === "object" ? exports : semver = {})