var Bitmap =  function(source, context) {
  this.initialize.apply(this, arguments);
};

var ConvolutionFilter = function(matrix, divisor, bias) {
  this.initialize.apply(this, arguments);
};

var SNNFilter = function(radius) {
  this.initialize.apply(this, arguments);
};

var ImageUtils = function() {
  this.initialize.apply(this, arguments);
};

/**
 * Definition Class
 */
Bitmap.prototype = {
  initialize : function(source, context) {
    this.bitmapData = new Image();
    this.bitmapData.src = source;
    this.width = this.bitmapData.width;
    this.height = this.bitmapData.height;
    this.context = context;
    this.bitmapData.addEventListener('error', function() { alert("can't load image"); }, false);
  },
  applyFilter : function(filter) {
    try {
      this.context.drawImage(this.bitmapData, 0, 0);
      var src = this.context.getImageData(0, 0, this.width, this.height);
      var dst = this.context.createImageData(this.width, this.height);
      //var gray = context.createImageData(this.width, this.height);
      filter.apply(src, dst);
      this.context.putImageData(dst, 0, 0);
    }catch(e) {
      alert(e);
    }
  }
};

ConvolutionFilter.prototype = {
  initialize : function(matrix, divisor, bias) {
    this.matrix = matrix;
    this.divisor = divisor;
    this.bias = bias;
  },
  apply : function(src, dst) {
    var w = src.width, h = src.height;
    var srcData = src.data;
    var dstData = dst.data;

    for(var y=1;y<h-1;++y) {
      for(var x=1;x<w-1;++x) {
        var idx = 0;
        var r = 0, g = 0, b = 0;
        //var d = 0;
        var i = (y*w + x) << 2;
        for(var ky=-1;ky<=1;++ky) {
          for(var kx=-1;kx<=1;++kx) {
            var pos = (ky*w << 2) + (kx << 2);
            r += srcData[i + pos]*this.matrix[idx];
            g += srcData[i + pos + 1]*this.matrix[idx];
            b += srcData[i + pos + 2]*this.matrix[idx];
            //d += srcData[i + pos]*this.matrix[idx++];
            idx++;
          }
        }
        dstData[i] = r/this.divisor + this.bias;
        dstData[i + 1] = g/this.divisor + this.bias;
        dstData[i + 2] = b/this.divisor + this.bias;
        dstData[i + 3] = 255;
      }
    }
    // for Firefox
    dstData.forEach(function(n, i, arr) { arr[i] = n<0 ? 0 : n>255 ? 255 : n; });
  }
};

SNNFilter.prototype = {
  initialize : function(radius) {
    this.radius = radius;
  },
  apply : function(src, dst) {
    var w = src.width, h = src.height;
    var srcData = src.data;
    var dstData = dst.data;
    var sunR, sumG, sumB;
    var rc, gc, bc, r1, g1, b1, r2, g2, b2;
    var cnt = 0;
    var xyPos, uvPos;

    for(var y=0;y<h;++y) {
      for(var x=0;x<w;++x) {
        xyPos = (w*y + x) << 2;
        sumR = 0, sumG = 0, sumB = 0;
        cnt = 0;
        rc = srcData[xyPos];
        gc = srcData[xyPos + 1];
        bc = srcData[xyPos + 2];
        for(var v=-this.radius;v<=this.radius;++v) {
          for(var u=-this.radius;u<=this.radius;++u,++cnt) {
            uvPos = (w*v + u) << 2;
            try {
              r1 = srcData[xyPos + uvPos];
              g1 = srcData[xyPos + uvPos + 1];
              b1 = srcData[xyPos + uvPos + 2];
              r2 = srcData[xyPos - uvPos];
              g2 = srcData[xyPos - uvPos + 1];
              b2 = srcData[xyPos - uvPos + 2];
            }catch(e) {
              break;
            }
            if(this.delta.apply(this, [rc, gc, bc, r1, g1, b1]) < this.delta.apply(this, [rc, gc, bc, r2, g2, b2])) {
              sumR += r1;
              sumG += g1;
              sumB += b2;
            }else {
              sumR += r2;
              sumG += g2;
              sumB += b2;
            }
          }
        }
        dstData[xyPos] = sumR/cnt;
        dstData[xyPos + 1] = sumG/cnt;
        dstData[xyPos + 2] = sumB/cnt;
        dstData[xyPos + 3] = 255;
      }
    }
  },
  delta : function(rc, gc, bc, r1, g1, b1) {
    return Math.sqrt((rc - r1)*(rc - r1) + (gc - g1)*(gc - g1) + (bc - b1)*(bc - b1));
  }
};

ImageUtils.prototype = {
  initialize : function() {
  },
  cvtColor : function(src, dst, mode) {
    var w = src.width, h = src.height;
    var srcData = src.data;
    var dstData = dst.data;
    switch(mode) {
    case "gray":
      for(var y=0;y<h;y++) {
        for(var x=0;x<w;x++) {
          var i = (y*w + x) << 2;
          dstData[i] = (77*srcData[i] + 150*srcData[i + 1] + 29*srcData[i + 2]) >> 8;
          dstData[i + 1] = dstData[i];
          dstData[i + 2] = dstData[i];
          dstData[i + 3] = 255;
        }
      }
      break;
    case "hsv":
      // TODO
      break;
    case "lab":
      // TODO
      break;
    default:
      break;
    }
  }
};