var RGB2XYZ = function(R, G, B) {
  if (R > 255) {
    console.warn('Red value was higher than 255. It has been set to 255.');
    R = 255;
  } else if (R < 0) {
    console.warn('Red value was smaller than 0. It has been set to 0.');
    R = 0;
  }
  if (G > 255) {
    console.warn('Green value was higher than 255. It has been set to 255.');
    G = 255;
  } else if (G < 0) {
    console.warn('Green value was smaller than 0. It has been set to 0.');
    G = 0;
  }
  if (B > 255) {
    console.warn('Blue value was higher than 255. It has been set to 255.');
    B = 255;
  } else if (B < 0) {
    console.warn('Blue value was smaller than 0. It has been set to 0.');
    B = 0;
  }
  var r = R / 255;
  var g = G / 255;
  var b = B / 255;
  // step 1
  if (r > 0.04045) {
    r = Math.pow(((r + 0.055) / 1.055), 2.4);
  } else {
    r = r / 12.92;
  }
  if (g > 0.04045) {
    g = Math.pow(((g + 0.055) / 1.055), 2.4);
  } else {
    g = g / 12.92;
  }
  if (b > 0.04045) {
    b = Math.pow(((b + 0.055) / 1.055), 2.4);
  } else {
    b = b / 12.92;
  }
  // step 2
  r = r * 100;
  g = g * 100;
  b = b * 100;
  // step 3
  var X = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
  var Y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
  var Z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
  return [X, Y, Z];
}
var XYZ2LAB = function(X, Y, Z) {
  // using 10o Observer (CIE 1964)
  // CIE10_D65 = {94.811f, 100f, 107.304f} => Daylight
  var ReferenceX = 94.811;
  var ReferenceY = 100;
  var ReferenceZ = 107.304;
  // step 1
  var x = X / ReferenceX;
  var y = Y / ReferenceY;
  var z = Z / ReferenceZ;
  // step 2
  if (x > 0.008856) {
    x = Math.pow(x, (1 / 3));
  } else {
    x = (7.787 * x) + (16 / 116);
  }
  if (y > 0.008856) {
    y = Math.pow(y, (1 / 3));
  } else {
    y = (7.787 * y) + (16 / 116);
  }
  if (z > 0.008856) {
    z = Math.pow(z, (1 / 3));
  } else {
    z = (7.787 * z) + (16 / 116);
  }
  // step 3
  var L = (116 * y) - 16;
  var A = 500 * (x - y);
  var B = 200 * (y - z);
  return [L, A, B];
};
function LAB2XYZ(L, A, B) {
  // using 10o Observer (CIE 1964)
  // CIE10_D65 = {94.811f, 100f, 107.304f} => Daylight
  var ReferenceX = 94.811;
  var ReferenceY = 100;
  var ReferenceZ = 107.304;

  var var_Y = ( L + 16 ) / 116;
  var var_X = A / 500 + var_Y;
  var var_Z = var_Y - B / 200;

  if ( Math.pow(var_Y, 3)  > 0.008856 ) {
    var_Y = Math.pow(var_Y, 3);
  } else {
    var_Y = ( var_Y - 16 / 116 ) / 7.787;
  }
  if ( Math.pow(var_X, 3)  > 0.008856 ) {
    var_X = Math.pow(var_X, 3);
  } else {
    var_X = ( var_X - 16 / 116 ) / 7.787;
  }
  if ( Math.pow(var_Z, 3)  > 0.008856 ) {
    var_Z = Math.pow(var_Z, 3);
  } else {
    var_Z = ( var_Z - 16 / 116 ) / 7.787;
  }

  var X = var_X * ReferenceX;
  var Y = var_Y * ReferenceY;
  var Z = var_Z * ReferenceZ;

  return [X, Y, Z];
}
var XYZ2RGB = function(X, Y, Z) {
  var var_X = X / 100;
  var var_Y = Y / 100;
  var var_Z = Z / 100;

  var var_R = (var_X *  3.2406) + (var_Y * -1.5372) + (var_Z * -0.4986);
  var var_G = (var_X * -0.9689) + (var_Y *  1.8758) + (var_Z *  0.0415);
  var var_B = (var_X *  0.0557) + (var_Y * -0.2040) + (var_Z *  1.0570);

  if ( var_R > 0.0031308 ) {
    var_R = 1.055 * Math.pow(var_R, (1 / 2.4) ) - 0.055;
  } else {
    var_R = 12.92 * var_R;
  }
  if ( var_G > 0.0031308 ) {
    var_G = 1.055 * Math.pow(var_G, (1 / 2.4) ) - 0.055;
  } else {
    var_G = 12.92 * var_G;
  }
  if ( var_B > 0.0031308 ) {
    var_B = 1.055 * Math.pow(var_B, (1 / 2.4) ) - 0.055;
  } else {
    var_B = 12.92 * var_B;
  }

  var R = Math.round(var_R * 255);
  var G = Math.round(var_G * 255);
  var B = Math.round(var_B * 255);

  return [R, G, B];
}
function rgbToLab(r,g,b) {
  return XYZ2LAB.apply(null, RGB2XYZ(r,g,b))
};
function labToRgb(r,g,b) {
  return XYZ2RGB.apply(null, LAB2XYZ(r,g,b))
};
function rgbToHsv(r, g, b) {
  r /= 255, g /= 255, b /= 255;

  var max = Math.max(r, g, b), min = Math.min(r, g, b);
  var h, s, v = max;

  var d = max - min;
  s = max == 0 ? 0 : d / max;

  if (max == min) {
    h = 0; // achromatic
  } else {
    switch (max) {
      case r: h = (g - b) / d + (g < b ? 6 : 0); break;
      case g: h = (b - r) / d + 2; break;
      case b: h = (r - g) / d + 4; break;
    }

    h /= 6;
  }

  return [ h, s, v ];
}
var labDistance = function(l1,l2) {
  return DeltaE00(l1,l2);
};
var DeltaE00 = function(lab1, lab2) {
  var l1 = lab1[0], a1 = lab1[1], b1 = lab1[2],
      l2 = lab2[0], a2 = lab2[1], b2 = lab2[2];
  // Utility functions added to Math Object
  Math.rad2deg = function(rad) {
    return 360 * rad / (2 * Math.PI);
  };
  Math.deg2rad = function(deg) {
    return (2 * Math.PI * deg) / 360;
  };
  // Start Equation
  // Equation exist on the following URL http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE2000.html
  var avgL = (l1 + l2) / 2;
  var C1 = Math.sqrt(Math.pow(a1, 2) + Math.pow(b1, 2));
  var C2 = Math.sqrt(Math.pow(a2, 2) + Math.pow(b2, 2));
  var avgC = (C1 + C2) / 2;
  var G = (1 - Math.sqrt(Math.pow(avgC, 7) / (Math.pow(avgC, 7) + Math.pow(25, 7)))) / 2;

  var A1p = a1 * (1 + G);
  var A2p = a2 * (1 + G);

  var C1p = Math.sqrt(Math.pow(A1p, 2) + Math.pow(b1, 2));
  var C2p = Math.sqrt(Math.pow(A2p, 2) + Math.pow(b2, 2));

  var avgCp = (C1p + C2p) / 2;

  var h1p = Math.rad2deg(Math.atan2(b1, A1p));
  if (h1p < 0) {
    h1p = h1p + 360;
  }

  var h2p = Math.rad2deg(Math.atan2(b2, A2p));
  if (h2p < 0) {
    h2p = h2p + 360;
  }

  var avghp = Math.abs(h1p - h2p) > 180 ? (h1p + h2p + 360) / 2 : (h1p + h1p) / 2;

  var T = 1 - 0.17 * Math.cos(Math.deg2rad(avghp - 30)) + 0.24 * Math.cos(Math.deg2rad(2 * avghp)) + 0.32 * Math.cos(Math.deg2rad(3 * avghp + 6)) - 0.2 * Math.cos(Math.deg2rad(4 * avghp - 63));

  var deltahp = h2p - h1p;
  if (Math.abs(deltahp) > 180) {
    if (h2p <= h1p) {
      deltahp += 360;
    } else {
      deltahp -= 360;
    }
  }

  var delta_lp = l2 - l1;
  var delta_cp = C2p - C1p;

  deltahp = 2 * Math.sqrt(C1p * C2p) * Math.sin(Math.deg2rad(deltahp) / 2);

  var Sl = 1 + ((0.015 * Math.pow(avgL - 50, 2)) / Math.sqrt(20 + Math.pow(avgL - 50, 2)));
  var Sc = 1 + 0.045 * avgCp;
  var Sh = 1 + 0.015 * avgCp * T;

  var deltaro = 30 * Math.exp(-(Math.pow((avghp - 275) / 25, 2)));
  var Rc = 2 * Math.sqrt(Math.pow(avgCp, 7) / (Math.pow(avgCp, 7) + Math.pow(25, 7)));
  var Rt = -Rc * Math.sin(2 * Math.deg2rad(deltaro));

  var kl = 1;
  var kc = 1;
  var kh = 1;

  var deltaE = Math.sqrt(Math.pow(delta_lp / (kl * Sl), 2) + Math.pow(delta_cp / (kc * Sc), 2) + Math.pow(deltahp / (kh * Sh), 2) + Rt * (delta_cp / (kc * Sc)) * (deltahp / (kh * Sh)));

  return deltaE;
};


/**
 * Converts an HSV color value to RGB. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSV_color_space.
 * Assumes h, s, and v are contained in the set [0, 1] and
 * returns r, g, and b in the set [0, 255].
 *
 * @param   Number  h       The hue
 * @param   Number  s       The saturation
 * @param   Number  v       The value
 * @return  Array           The RGB representation
 */
function hsvToRgb(h, s, v) {
  var r, g, b;

  var i = Math.floor(h * 6);
  var f = h * 6 - i;
  var p = v * (1 - s);
  var q = v * (1 - f * s);
  var t = v * (1 - (1 - f) * s);

  switch (i % 6) {
    case 0: r = v, g = t, b = p; break;
    case 1: r = q, g = v, b = p; break;
    case 2: r = p, g = v, b = t; break;
    case 3: r = p, g = q, b = v; break;
    case 4: r = t, g = p, b = v; break;
    case 5: r = v, g = p, b = q; break;
  }

  return [ r * 255, g * 255, b * 255 ];
}

/**
 * Converts an RGB color value to HSL. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
 * Assumes r, g, and b are contained in the set [0, 255] and
 * returns h, s, and l in the set [0, 1].
 *
 * @param   Number  r       The red color value
 * @param   Number  g       The green color value
 * @param   Number  b       The blue color value
 * @return  Array           The HSL representation
 */
function rgbToHsl(r, g, b) {
  r /= 255, g /= 255, b /= 255;

  var max = Math.max(r, g, b), min = Math.min(r, g, b);
  var h, s, l = (max + min) / 2;

  if (max === min) {
    h = s = 0; // achromatic
  } else {
    var d = max - min;
    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);

    switch (max) {
      case r: h = (g - b) / d + (g < b ? 6 : 0); break;
      case g: h = (b - r) / d + 2; break;
      case b: h = (r - g) / d + 4; break;
    }

    h /= 6;
  }

  return [ h, s, l ];
}

var colorsFromPoints = function(points, ctx, where, k, production, notThis, partName) {

  var deltaSkin = 15;
  if(partName === 'blush')
    deltaSkin = 8;
  if(partName === 'eyes')
    deltaSkin = 15;
  var minX = Infinity, minY = Infinity,
    maxX = -Infinity, maxY = -Infinity;

  for( var i = 0, _i = points.length; i < _i; i++ ){
    var point = points[ i ];
    if( minX > point[ 0 ] ) minX = point[ 0 ];
    if( minY > point[ 1 ] ) minY = point[ 1 ];
    if( maxX < point[ 0 ] ) maxX = point[ 0 ];
    if( maxY < point[ 1 ] ) maxY = point[ 1 ];
  }

  var w = maxX - minX + 1;
  var imageData = ctx.getImageData( minX, minY, w, maxY - minY + 1 ),
    data = imageData.data;

  var r, g, b;
  var colorsRaw = [];
  if( !production ){
    var debugPixels = document.getElementById( 'ccolors' ).getContext( '2d' );
    var debugData = debugPixels.getImageData( 0, 0, 100, 480 ),
      dd = debugData.data;
  }
  var pt = ( extracting * 100 * 120 * 4 )
  var colors = [];

  for( var i = 0, _i = points.length; i < _i; i++ ){
    var p = points[ i ], pointer = ( p[ 0 ] - minX + ( p[ 1 ] - minY ) * w ) * 4;
    r = data[ pointer ];
    g = data[ pointer + 1 ];
    b = data[ pointer + 2 ];
    if( !production ){
      pt = ( ( extracting * 100 * 120 + i * 2 + ( ( i / 50 ) | 0 ) * 100 ) * 4 );
      dd[ pt++ ] = r;
      dd[ pt++ ] = g;
      dd[ pt++ ] = b;
      dd[ pt++ ] = 255;

      dd[ pt++ ] = r;
      dd[ pt++ ] = g;
      dd[ pt++ ] = b;
      dd[ pt++ ] = 255;
      pt = ( ( extracting * 100 * 120 + i * 2 + ( ( i / 50 ) | 0 ) * 100 ) * 4 ) + 100 * 4;
      dd[ pt++ ] = r;
      dd[ pt++ ] = g;
      dd[ pt++ ] = b;
      dd[ pt++ ] = 255;

      dd[ pt++ ] = r;
      dd[ pt++ ] = g;
      dd[ pt++ ] = b;
      dd[ pt++ ] = 255;
    }


    //data[ pointer + 2 ] = 255
    if(notThis){
      var near = false, lab = rgbToLab(r,g,b);
      for( var j = 0, _j = notThis.length; j < _j; j++ ){
        var notThi = notThis[ j ];
        if(labDistance(notThi.hsv, lab)<deltaSkin){
          near = true;
          break;
        }
      }
      if(near === false){
        colorsRaw.push( { rgb: [ r, g, b ], hsl: rgbToHsl( r, g, b ) } );
      }
    }else{
      colorsRaw.push( { rgb: [ r, g, b ], hsl: rgbToHsl( r, g, b ) } );
    }
//    colors.push(rgbToLab(r,g,b));
  }

  var bucketsCount = 100; var buckets = [];
  for( var i = 0; i < bucketsCount; i++ ){
    buckets.push(0);
  }

  var minL = Infinity, maxL = 0.8;
  for( var i = 0, _i = colorsRaw.length; i < _i; i++ ){
    var colorsRawElement = colorsRaw[ i ],
      L = colorsRawElement.hsl[2];
    (L < minL) && (minL = L);
    (L > maxL) && (maxL = L);
    buckets[L*bucketsCount|0]++;
  }


  var dL = maxL - minL, centerL = (maxL+minL)/2;
  for( var i = 0, _i = colorsRaw.length; i < _i; i++ ){
    var colorsRawElement = colorsRaw[ i ],
      L = colorsRawElement.hsl[2];
    if(buckets[L*bucketsCount|0]>_i/bucketsCount/2)
      //if( L > minL+dL/5 && L < maxL - dL/3 && L < 0.75 )
      colors.push( rgbToLab.apply( null, colorsRawElement.rgb ) );

  }

  //rgbToLab( r, g, b )
  if( !production ){
    debugPixels.putImageData( debugData, 0, 0 );
  }
  //ctx2.putImageData( imageData, minX, minY );


  var means = new KMeans();

  var result = { means: means, clusters: means.cluster( colors, k, labDistance ) };
  var amount = 0;
  for( var i = 0, _i = result.clusters.length; i < _i; i++ ){
    var cluster = result.clusters[ i ] || [];
    amount += cluster.length;
  }

  var filteredResult = result.clusters.map( function( c, i ){
    return { color: c, normalized: c.length / amount };
  } )
    .filter( function( c ){
      return c.normalized > 1 / ( k * 2.5 );
    } )
    .sort( function( a, b ){
      return a.normalized - b.normalized;
    } )
    .map( function( c ){
      var l = 0, a = 0, b = 0;
      for( var i = 0, _i = c.color.length; i < _i; i++ ){
        var colorElement = c.color[ i ];
        l+=colorElement[0];
        a+=colorElement[1];
        b+=colorElement[2];
      }
      return [
        l/c.color.length,
        a/c.color.length,
        b/c.color.length
      ];
      //c.color.sort( colorSort )[ 0 ];//result.means.centroids.slice().sort(distanceToHSV(c.color[0]))[0];
    } );

  var similars = filteredResult.map( function( c, i ){
    return { color: c, i: i, similar: [ i ] }
  } );


  // O^2 similarity search
  for( var i = 0, _i = similars.length; i < _i; i++ ){
    var c1 = similars[ i ];
    for( var j = 0, _j = similars.length; j < _j; j++ ){
      if( i === j )
        continue;
      var c2 = similars[ j ];
      if( Math.abs( labDistance( c1.color, c2.color ) ) < similarColor ){
        if( c1.similar.indexOf( j ) === -1 ){
          c1.similar.push( j );
          c2.similar.push( i );
        }
      }
    }
  }
  var used = {};

  var different = similars.reduce( function( store, c ){
    if( !( c.i in used ) )
      store.push( c );

    for( var i = 0, _i = c.similar.length; i < _i; i++ ){
      var similarElement = c.similar[ i ];
      var item = similars[ similarElement ];
      if( !( item.i in used ) ){
        used[ item.i ] = true;
      }
    }
    return store;
  }, [] );


  filteredResult = different.map( function( item ){
    return item.color;
  } );


  return filteredResult.map( function( color ){
    return { color: color, distance: labDistance( color, filteredResult[ 0 ] ) };
  } ).filter( function( color ){
    var delta = Math.abs( color.distance );
    return delta < 0.0000001 || delta > 15;
  } );
};
var extractColors = function( ctx, where, k, production, notThis, partName){
  if(partName === 'skin'){

    var colors = where.map( function( polygon ){
      return colorsFromPoints(triangle.apply( null, polygon ), ctx, where, k, production, notThis, partName);
    } );

    var min = Infinity, max = -Infinity;
    var colors = [].concat.apply([], colors);
    for( var i = 0, _i = colors.length; i < _i; i++ ){
      var color = colors[ i ].color;
      min = Math.min(min, color[0]);
      max = Math.max(max, color[0]);
    }

    var delta = max-min;
    colors = colors.filter(function(c) {
      return c.color[0] >= min + delta/10 && c.color[0] <= max + delta/10;
    });
    var l = 0, a = 0, b = 0;
    for( var i = 0, _i = colors.length; i < _i; i++ ){
      var color1 = colors[ i ].color;
      l+=color1[0]; a+=color1[1]; b+=color1[2];
    }

    l/= _i; a /= _i; b /= _i;
    return [{color: [l,a,b]}];
  }
  var points = [].concat.apply( [], where.map( function( polygon ){
    return triangle.apply( null, polygon );
  } ) );

  return colorsFromPoints(points, ctx, where, k, production, notThis, partName);
};
var colorSort = function(a,b){return (-(a[2]-b[2]))+(-(a[1]-b[1])/2)};
var similarColor = 5;//0.05;
var abs = Math.abs, min = Math.min;
var hsvDistance = function( c1, c2 ){
  var dh = min( abs( c2[ 0 ] - c1[ 0 ] ), 1 - abs( c2[ 0 ] - c1[ 0 ] ) ),
    ds = abs( c2[ 1 ] - c1[ 1 ] ), dv = c2[ 2 ] - c1[ 2 ];

  return Math.sqrt(Math.pow(dh/2,2) + Math.pow(ds / 8, 2) + Math.pow(dv / 4,2));
};


function GetPixel(context, x, y)
{
  var p = context.getImageData(x, y, 1, 1).data;
  var hex = "#" + ("000000" + rgbToHex(p[0], p[1], p[2])).slice(-6);
  return hex;
}

function rgbToHex(r, g, b) {
  if (r > 255 || g > 255 || b > 255)
    throw "Invalid color component";
  return ((r << 16) | (g << 8) | b).toString(16);
}
var binds = [];
var extractor = function(partName, ctx, from, k, production, notThis) {
  extracting++;
  var colors = extractColors(ctx, from, k, production, notThis, partName)
    .map(function(m) {
      return {color: labToRgb.apply(null, m.color).map(function(a){return a|0}), hsv: m.color, distance: m.distance};
    });

  binds.forEach(function(b) {
    b();
  });

  binds = [];

  var mean = 0, count = colors.length;
  for( var i = 0, _i = colors.length; i < _i; i++ ){
    var color = colors[ i ];
    mean+=color.hsv[0];//L
  }
  mean /= count;

  if(partName === 'eyes' || partName === 'lips'){
    if(mean>20){
      colors = colors.filter(function(c) {
        return c.hsv[0] > 10;
      })
    }
    if(mean<88){
      colors = colors.filter(function(c) {
        return c.hsv[0] < 90;
      })
    }
  }
  if(partName === 'skin'){

    colors = colors.filter(function(c) {
      return c.hsv[0] > mean-10;
    });
    count = colors.length;
    var mean = [0,0,0];
    for( var i = 0, _i = colors.length; i < _i; i++ ){
      var color = colors[ i ];
      mean[0]+=color.hsv[0];//L
      mean[1]+=color.hsv[1];//L
      mean[2]+=color.hsv[2];//L
    }
    mean[0] /= count;
    mean[1] /= count;
    mean[2] /= count;
    colors = [{color: labToRgb.apply(null, mean), hsv: mean}]
  }
  if(production !== 0 && production !== void 0){

    var clrs = document.getElementById(partName+'-bg').querySelector('.colors');
    clrs && clrs.parentNode.removeChild(clrs);
    D.div({cls: 'colors', renderTo: document.getElementById(partName+'-bg') },
      colors.map(function(m){
        return D.div({style: 'background:rgb('+m.color.join(',')+');width:'+100/colors.length+'%', cls: 'color-preview'},
          'L: '+Math.floor(m.hsv[0]),D.h('br'),
          'A: '+Math.round(m.hsv[1]), D.h('br'),
          'B: '+Math.round(m.hsv[2]), D.h('br'),
          (notThis||[]).map(function(c) {
            return labDistance(c.hsv, m.hsv).toFixed(2);
          }).join('~')
        );
      })
    );

    if(production === 2){
      return colors;
    }

    if(partName[0] !== '_'){
      loadJSONcollection( partName, colors );
    }

    return;
  }

  return D.div({cls: 'colors'},
    colors.map(function(m){
      return D.span({style: 'background:rgb('+m.color.join(',')+');', cls: 'color-preview'},

        'L: '+Math.floor(m.hsv[0]),D.h('br'),
        'A: '+Math.round(m.hsv[1]), D.h('br'),
        'B: '+Math.round(m.hsv[2]), D.h('br'),
        (notThis||[]).map(function(c) {
          debugger
          return labDistance(c, m.hsv);
        })
          /*'H: '+Math.floor(m.hsv[0]*360),D.h('br'),
          'S: '+Math.round(m.hsv[1]*100)+'%', D.h('br'),
          'V: '+Math.round(m.hsv[2]*100)+'%'*/
      );
    }),
    function(draw) {
      getData(partName, function(res){
        if(!res)
          return;
        var b = store.sub( [ 'threshold', 'maxCount', 'maxThreshold' ], function( threshold, maxCount, maxThreshold ){

          var list = [];
          res.products.forEach( function( product ){
            product.variants.forEach( function( variant ){
              var color = variant.option2.substr( 1 ),
                  r = parseInt( color[ 0 ] + color[ 1 ], 16 ),
                  g = parseInt( color[ 2 ] + color[ 3 ], 16 ),
                  b = parseInt( color[ 4 ] + color[ 4 ], 16 );

              list.push( {
                pid: product.id,
                vid: variant.id,
                description: product.body_html,
                title: product.title,
                color: variant.option2,
                colorName: variant.option1,
                img: variant.featured_image && variant.featured_image.src || ( product.images[ 0 ] || {} ).src,
                sku: variant.option3,
                hsv: rgbToLab( r, g, b )
              } );
            } );
          } );
          var used = {};
          var all = [].concat.apply( [],
            colors.map( function( clr ){
              var epsilon = threshold;
              var matched = list.map( function( i ){
                return { i: i, d: labDistance( i.hsv, clr.hsv ), c: clr };
              } )
                .filter( function( i ){
                  return i.d <= maxThreshold;
                } )
                .sort( function( a, b ){
                  return a.d - b.d;
                } );
              var allMatched = matched.slice();
              if(maxCount === 0)
                return matched;

              matched = allMatched.filter(function(a) {
                return a.d <= threshold;
              });

              var lastMatched = matched;
              var m = 0, step = 0;
              while( matched.length < maxCount ){
                step++;
                epsilon = epsilon + 0.25;

                if(epsilon > maxThreshold)
                  break;

                if(m*step/5+epsilon>maxThreshold)
                  break;

                if(matched.length && epsilon>(threshold+maxThreshold)/2)
                  break;

                matched = allMatched.filter( function( i ){
                  return i.d <= epsilon;
                } );

                m = Math.max(m, matched.length);

                lastMatched = matched;
              }
              /*if(!matched.length>3){
                matched = matched.f;
                return matched;
              }*/
              /*if(matched[0].d<0.02){
                return matched.slice( 0, 1 )
              }else{
                return matched.slice(0, 2);
              }*/
              return matched.slice(0,maxCount);
            } ) ).filter( function( item ){
            if( !( item.i.vid in used ) ){
              used[ item.i.vid ] = true;
              return true;
            }
            return false;
          } );

          var count = all.length;

          draw( D.div( { cls: 'matched opened' },
            D.span( {
              cls: 'action', onClick: function(){
                this.parentNode.classList.toggle( 'opened' );
              }
            }, 'Matched: ' + count + ' ' + ( count === 1 ? 'item' : 'items' ) ),
            D.div( { cls: 'collapsed' },
              all.map( function( item ){
                return D.div( { cls: 'row' },
                  D.div( { cls: 'cp1', style: { background: 'rgb(' + item.c.color + ')' } } ),
                  D.div( { cls: 'cp2', style: { background: item.i.color } } ),
                  D.h( 'img', { cls: 'preview-img', src: item.i.img } ),
                  D.div( { cls: 'name' }, item.i.title ),
                  D.div( { cls: 'accuracy' }, (100-item.d).toFixed( 2 ) + '%' )
                )
              } )
            )
          ) );
        } );

        //console.log(b)
        binds.push(b);
      });


    }
  );
};