

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

  var deltaSkin = 15;
  if(partName === 'blush')
    deltaSkin = 8;
  if(partName === 'eyes')
    deltaSkin = 15;
  if(partName === 'other')
    deltaSkin = 4;
  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 );
  } ) );

  var clrs = colorsFromPoints(points, ctx, where, k, production, notThis, partName);
  if(partName === 'blush' && clrs.length === 0){
    clrs = colorsFromPoints(points, ctx, where, k, production, notThis, 'other');
    if(clrs.length === 0){
      clrs = notThis;
    }
  }
  return clrs;
};
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){
    if(partName[0] !== '_' && partName !== 'skin'){
      document.getElementById( partName + '-bg' ).style.display = colors.length ? 'block' : 'none';
    }

    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);
      });


    }
  );
};