(function () {
  var ns = $.namespace("pskl");

  ns.PixelUtils = {

    getRectanglePixels : function (x0, y0, x1, y1) {
      var rectangle = this.getOrderedRectangleCoordinates(x0, y0, x1, y1);
      var pixels = [];

      for(var x = rectangle.x0; x <= rectangle.x1; x++) {
        for(var y = rectangle.y0; y <= rectangle.y1; y++) {
          pixels.push({"col": x, "row": y});
        }
      }
      
      return pixels;      
    },

    getBoundRectanglePixels : function (x0, y0, x1, y1) {
      var rectangle = this.getOrderedRectangleCoordinates(x0, y0, x1, y1);
      var pixels = [];
      // Creating horizontal sides of the rectangle:
      for(var x = rectangle.x0; x <= rectangle.x1; x++) {
        pixels.push({"col": x, "row": rectangle.y0});
        pixels.push({"col": x, "row": rectangle.y1});
      }

      // Creating vertical sides of the rectangle:
      for(var y = rectangle.y0; y <= rectangle.y1; y++) {
        pixels.push({"col": rectangle.x0, "row": y});
        pixels.push({"col": rectangle.x1, "row": y});
      }
      
      return pixels;  
    },

    /**
     * Return an object of ordered rectangle coordinate.
     * In returned object {x0, y0} => top left corner - {x1, y1} => bottom right corner 
     * @private
     */
    getOrderedRectangleCoordinates : function (x0, y0, x1, y1) {
      return {
        x0 : Math.min(x0, x1), 
        y0 : Math.min(y0, y1),
        x1 : Math.max(x0, x1), 
        y1 : Math.max(y0, y1),
      };
    },

    /**
     * Return the list of pixels that would have been filled by a paintbucket tool applied 
     * on pixel at coordinate (x,y).
     * This function is not altering the Frame object argument. 
     *
     * @param frame pskl.model.Frame The frame target in which we want to paintbucket
     * @param col number Column coordinate in the frame 
     * @param row number Row coordinate in the frame 
     *
     * @return an array of the pixel coordinates paint with the replacement color
     */
    getSimilarConnectedPixelsFromFrame: function(frame, col, row) {
      // To get the list of connected (eg the same color) pixels, we will use the paintbucket algorithm
      // in a fake cloned frame. The returned pixels by the paintbucket algo are the painted pixels
      // and are as well connected.
      var fakeFrame = frame.clone(); // We just want to
      var fakeFillColor = "sdfsdfsdf"; // A fake color that will never match a real color.
      var paintedPixels = this.paintSimilarConnectedPixelsFromFrame(fakeFrame, col, row, fakeFillColor);

      return paintedPixels;
    },

    /**
     * Apply the paintbucket tool in a frame at the (col, row) initial position
     * with the replacement color.
     * 
     * @param frame pskl.model.Frame The frame target in which we want to paintbucket
     * @param col number Column coordinate in the frame 
     * @param row number Row coordinate in the frame 
     * @param replacementColor string Hexadecimal color used to fill the area
     *
     * @return an array of the pixel coordinates paint with the replacement color
     */
    paintSimilarConnectedPixelsFromFrame: function(frame, col, row, replacementColor) {
      /**
       *  Queue linear Flood-fill (node, target-color, replacement-color):
       *   1. Set Q to the empty queue.
       *   2. If the color of node is not equal to target-color, return.
       *   3. Add node to Q.
       *   4. For each element n of Q:
       *   5.     If the color of n is equal to target-color:
       *   6.         Set w and e equal to n.
       *   7.         Move w to the west until the color of the node to the west of w no longer matches target-color.
       *   8.         Move e to the east until the color of the node to the east of e no longer matches target-color.
       *   9.         Set the color of nodes between w and e to replacement-color.
       *  10.         For each node n between w and e:
       *  11.             If the color of the node to the north of n is target-color, add that node to Q.
       *  12.             If the color of the node to the south of n is target-color, add that node to Q.
       *  13. Continue looping until Q is exhausted.
       *  14. Return.
       */
      var paintedPixels = [];
      var queue = [];
      var dy = [-1, 0, 1, 0];
      var dx = [0, 1, 0, -1];
      var targetColor;
      try {
        targetColor = frame.getPixel(col, row);
      } catch(e) {
        // Frame out of bound exception.
      }
      
      if(targetColor == replacementColor) {
        return;
      }
      

      queue.push({"col": col, "row": row});
      var loopCount = 0;
      var cellCount = frame.getWidth() * frame.getHeight();
      while(queue.length > 0) {
        loopCount ++;

        var currentItem = queue.pop();
        frame.setPixel(currentItem.col, currentItem.row, replacementColor);
        paintedPixels.push({"col": currentItem.col, "row": currentItem.row });

        for (var i = 0; i < 4; i++) {
          var nextCol = currentItem.col + dx[i];
          var nextRow = currentItem.row + dy[i];
          try {
            if (frame.containsPixel(nextCol, nextRow)  && frame.getPixel(nextCol, nextRow) == targetColor) {
              queue.push({"col": nextCol, "row": nextRow });
            }
          } catch(e) {
            // Frame out of bound exception.
          }
        }

        // Security loop breaker:
        if(loopCount > 10 * cellCount) {
          console.log("loop breaker called");
          break;          
        }
      }
      return paintedPixels;
    },

    /**
     * Calculate and return the maximal DPI to display a picture in a given container.
     *
     * @param container jQueryObject Container where the picture should be displayed
     * @param number pictureHeight height in pixels of the picture to display
     * @param number pictureWidth width in pixels of the picture to display
     * @return number maximal dpi
     */
    calculateDPIForContainer : function (container, pictureHeight, pictureWidth) {
      return this.calculateDPI(container.height(), container.width(), pictureHeight, pictureWidth);
    },

    /**
     * Calculate and return the maximal DPI to display a picture for a given height and width.
     *
     * @param height number Height available to display the picture
     * @param width number Width available to display the picture
     * @param number pictureHeight height in pixels of the picture to display
     * @param number pictureWidth width in pixels of the picture to display
     * @return number maximal dpi
     */
    calculateDPI : function (height, width, pictureHeight, pictureWidth) {
      var heightBoundDpi = Math.floor(height / pictureHeight),
        widthBoundDpi = Math.floor(width / pictureWidth);

      return Math.min(heightBoundDpi, widthBoundDpi);
    },
  };
})();