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


/**
 * 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 ];
}


var extractColors = function( ctx, where, k, debug){


  var points = [].concat.apply( [], where.map( function( polygon ){
    return triangle.apply( null, polygon );
  } ) );

  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 colors = [];

  var debugPixels = document.getElementById('ccolors').getContext('2d');
  var debugData = debugPixels.getImageData( 0, 0, 100, 480 ),
    dd = debugData.data;
  var pt = (extracting*100*120*4)

  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 ];

    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
    colors.push( rgbToHsv( r, g, b ) );
  }

  debugPixels.putImageData( debugData, 0, 0 );
  //ctx2.putImageData( imageData, minX, minY );


  var means = new KMeans();

  var result = { means: means, clusters: means.cluster( colors, k, hsvDistance ) };
  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*3);
    } )
    .sort( function( a, b ){
      return a.normalized - b.normalized;
    } )
    .map( function( c ){
      return 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(hsvDistance(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;
  }, []);

  if(debug){
    similars, result
  }

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


  return filteredResult.map(function(color) {
    return {color: color, distance: hsvDistance(color, filteredResult[0])};
  }).filter(function(color) {
    var delta = Math.abs(color.distance);
    return delta <0.0000001 || delta > 0.03;
  });
};
var colorSort = function(a,b){return (-(a[2]-b[2]))+(-(a[1]-b[1])/2)};
var similarColor = 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 extractor = function(partName, ctx, from, k, debug) {
  extracting++;
  var colors = extractColors(ctx, from, k, debug)
    .map(function(m) {
      return {color: hsvToRgb.apply(null, m.color).map(function(a){return a|0}), hsv: m.color, distance: m.distance};
    });



  return D.div({cls: 'colors'},
    colors.map(function(m){
      return D.span({style: 'background:rgb('+m.color.join(',')+');', cls: 'color-preview'},
          '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) {
        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: rgbToHsv(r,g,b)
            });
          });
        });
        var used = {};
        var all = [].concat.apply([],
          colors.map(function(clr) {
            var epsilon = 0.033;
            var matched = list.map(function(i) {
                return {i: i, d: hsvDistance(i.hsv, clr.hsv), c: clr};
              })
              .filter(function(i) {
                return i.d<epsilon;
              })
              .sort(function(a,b) {
                return a.d-b.d;
              });
            var lastMatched = matched;
            while(matched.length > 5){
              epsilon = epsilon/3*2;

              matched = matched.filter(function(i) {
                return i.d<epsilon;
              });
              if(matched.length===0){
                matched = lastMatched;
                break;
              }
              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;
        })).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'},
          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'}, (Math.max(0,Math.min(100,(1-item.d*8)*100))).toFixed(2)+'%')
              )
            })
          )

        ));

      });


    }
  );
};