// Generated by IcedCoffeeScript 1.3.3f
(function() {
  var SequenceMatcher, arrayDiff, colorize, descalarize, diff, diffScore, diffString, diffWithScore, extendedTypeOf, findMatchingObject, isScalar, isScalarized, objectDiff, scalarize,
    __hasProp = {}.hasOwnProperty;

  SequenceMatcher = require('difflib').SequenceMatcher;

  extendedTypeOf = require('./util').extendedTypeOf;

  colorize = require('./colorize').colorize;

  isScalar = function(obj) {
    return typeof obj !== 'object';
  };

  objectDiff = function(obj1, obj2) {
    var change, key, keys1, keys2, result, score, subscore, value1, value2, _ref, _ref1;
    result = {};
    score = 0;
    keys1 = Object.keys(obj1);
    keys2 = Object.keys(obj2);
    for (key in obj1) {
      if (!__hasProp.call(obj1, key)) continue;
      value1 = obj1[key];
      if (!(!(key in obj2))) continue;
      result["" + key + "__deleted"] = value1;
      score -= 30;
    }
    for (key in obj2) {
      if (!__hasProp.call(obj2, key)) continue;
      value2 = obj2[key];
      if (!(!(key in obj1))) continue;
      result["" + key + "__added"] = value2;
      score -= 30;
    }
    for (key in obj1) {
      if (!__hasProp.call(obj1, key)) continue;
      value1 = obj1[key];
      if (!(key in obj2)) continue;
      score += 20;
      value2 = obj2[key];
      _ref = diffWithScore(value1, value2), subscore = _ref[0], change = _ref[1];
      if (change) result[key] = change;
      score += Math.min(20, Math.max(-10, subscore / 5));
    }
    if (Object.keys(result).length === 0) {
      _ref1 = [100 * Object.keys(obj1).length, void 0], score = _ref1[0], result = _ref1[1];
    } else {
      score = Math.max(0, score);
    }
    return [score, result];
  };

  findMatchingObject = function(item, fuzzyOriginals) {
    var bestMatch, candidate, key, score;
    bestMatch = null;
    for (key in fuzzyOriginals) {
      if (!__hasProp.call(fuzzyOriginals, key)) continue;
      candidate = fuzzyOriginals[key];
      if (key !== '__next') {
        if (extendedTypeOf(item) === extendedTypeOf(candidate)) {
          score = diffScore(item, candidate);
          if (!bestMatch || score > bestMatch.score) {
            bestMatch = {
              score: score,
              key: key
            };
          }
        }
      }
    }
    return bestMatch;
  };

  scalarize = function(array, originals, fuzzyOriginals) {
    var bestMatch, item, proxy, _i, _len, _results;
    _results = [];
    for (_i = 0, _len = array.length; _i < _len; _i++) {
      item = array[_i];
      if (isScalar(item)) {
        _results.push(item);
      } else if (fuzzyOriginals && (bestMatch = findMatchingObject(item, fuzzyOriginals)) && bestMatch.score > 40) {
        originals[bestMatch.key] = item;
        _results.push(bestMatch.key);
      } else {
        proxy = "__$!SCALAR" + originals.__next++;
        originals[proxy] = item;
        _results.push(proxy);
      }
    }
    return _results;
  };

  isScalarized = function(item, originals) {
    return (typeof item === 'string') && (item in originals);
  };

  descalarize = function(item, originals) {
    if (isScalarized(item, originals)) {
      return originals[item];
    } else {
      return item;
    }
  };

  arrayDiff = function(obj1, obj2, stats) {
    var allEqual, change, i, i1, i2, item, item1, item2, j, j1, j2, op, opcodes, originals1, originals2, result, score, seq1, seq2, _i, _j, _k, _l, _len, _m, _n, _ref;
    originals1 = {
      __next: 1
    };
    seq1 = scalarize(obj1, originals1);
    originals2 = {
      __next: originals1.__next
    };
    seq2 = scalarize(obj2, originals2, originals1);
    opcodes = new SequenceMatcher(null, seq1, seq2).getOpcodes();
    result = [];
    score = 0;
    allEqual = true;
    for (_i = 0, _len = opcodes.length; _i < _len; _i++) {
      _ref = opcodes[_i], op = _ref[0], i1 = _ref[1], i2 = _ref[2], j1 = _ref[3], j2 = _ref[4];
      if (op !== 'equal') allEqual = false;
      switch (op) {
        case 'equal':
          for (i = _j = i1; i1 <= i2 ? _j < i2 : _j > i2; i = i1 <= i2 ? ++_j : --_j) {
            item = seq1[i];
            if (isScalarized(item, originals1)) {
              if (!isScalarized(item, originals2)) {
                throw new AssertionError("internal bug: isScalarized(item, originals1) != isScalarized(item, originals2) for item " + (JSON.stringify(item)));
              }
              item1 = descalarize(item, originals1);
              item2 = descalarize(item, originals2);
              change = diff(item1, item2);
              if (change) {
                result.push(['~', change]);
                allEqual = false;
              } else {
                result.push([' ']);
              }
            } else {
              result.push([' ', item]);
            }
            score += 10;
          }
          break;
        case 'delete':
          for (i = _k = i1; i1 <= i2 ? _k < i2 : _k > i2; i = i1 <= i2 ? ++_k : --_k) {
            result.push(['-', descalarize(seq1[i], originals1)]);
            score -= 5;
          }
          break;
        case 'insert':
          for (j = _l = j1; j1 <= j2 ? _l < j2 : _l > j2; j = j1 <= j2 ? ++_l : --_l) {
            result.push(['+', descalarize(seq2[j], originals2)]);
            score -= 5;
          }
          break;
        case 'replace':
          for (i = _m = i1; i1 <= i2 ? _m < i2 : _m > i2; i = i1 <= i2 ? ++_m : --_m) {
            result.push(['-', descalarize(seq1[i], originals1)]);
            score -= 5;
          }
          for (j = _n = j1; j1 <= j2 ? _n < j2 : _n > j2; j = j1 <= j2 ? ++_n : --_n) {
            result.push(['+', descalarize(seq2[j], originals2)]);
            score -= 5;
          }
      }
    }
    if (allEqual || (opcodes.length === 0)) {
      result = void 0;
      score = 100;
    } else {
      score = Math.max(0, score);
    }
    return [score, result];
  };

  diffWithScore = function(obj1, obj2) {
    var type1, type2;
    type1 = extendedTypeOf(obj1);
    type2 = extendedTypeOf(obj2);
    if (type1 === type2) {
      switch (type1) {
        case 'object':
          return objectDiff(obj1, obj2);
        case 'array':
          return arrayDiff(obj1, obj2);
      }
    }
    if (obj1 !== obj2) {
      return [
        0, {
          __old: obj1,
          __new: obj2
        }
      ];
    } else {
      return [100, void 0];
    }
  };

  diff = function(obj1, obj2) {
    var change, score, _ref;
    _ref = diffWithScore(obj1, obj2), score = _ref[0], change = _ref[1];
    return change;
  };

  diffScore = function(obj1, obj2) {
    var change, score, _ref;
    _ref = diffWithScore(obj1, obj2), score = _ref[0], change = _ref[1];
    return score;
  };

  diffString = function(obj1, obj2, options) {
    return colorize(diff(obj1, obj2), options);
  };

  module.exports = {
    diff: diff,
    diffString: diffString
  };

}).call(this);
