/*
 * Copyright (c) 2015, Leon Sorokin
 * All rights reserved. (MIT Licensed)
 *
 * ColorHistogram.js - an image quantization lib
 */

/**
 * @preserve TypeScript port:
 * Copyright 2015 Igor Bezkrovny
 * All rights reserved. (MIT Licensed)
 *
 * colorHistogram.ts - part of Image Quantization Library
 */
/// <reference path='../../utils/hueStatistics.ts' />
module IQ.Palette {

    export class ColorHistogram {
        private static _boxSize = [64, 64];
        private static _boxPixels = 2;
        private static _hueGroups = 10;

        // 1 = by global population, 2 = subregion population threshold
        private _method:number;

        // HueStatistics instance
        private _hueStats:Utils.HueStatistics;

        private _histogram:{[color : string] : number};

        // # of highest-frequency colors to start with for palette reduction
        private _initColors:number;

        // if > 0, enables hues stats and min-color retention per group
        private _minHueCols:number;

        constructor(method:number, colors:number) {
            // 1 = by global population, 2 = subregion population threshold
            this._method = method;

            // if > 0, enables hues stats and min-color retention per group
            this._minHueCols = colors << 2;//opts.minHueCols || 0;

            // # of highest-frequency colors to start with for palette reduction
            this._initColors = colors << 2;

            // HueStatistics instance
            this._hueStats = new Utils.HueStatistics(ColorHistogram._hueGroups, this._minHueCols);

            this._histogram = Object.create(null);
        }

        public sample(pointBuffer:Utils.PointContainer):void {
            switch (this._method) {
                case 1:
                    this._colorStats1D(pointBuffer);
                    break;
                case 2:
                    this._colorStats2D(pointBuffer);
                    break;
            }
        }

        public getImportanceSortedColorsIDXI32() {
            var sorted = Arithmetic.stableSort(Object.keys(this._histogram), (a, b) => this._histogram[b] - this._histogram[a]);
            //var sorted = Object.keys(this._histogram).sort((a, b) => this._histogram[b] - this._histogram[a]);
            //var sorted = Utils.sortedHashKeys(this._histogram, true);

            // TODO: check that other code waits for null
            if (sorted.length == 0) {
                return null;
            }

            switch (this._method) {
                case 1:
                    var initialColorsLimit = Math.min(sorted.length, this._initColors),
                        last = sorted[initialColorsLimit - 1],
                        freq = this._histogram[last];

                    var idxi32 = sorted.slice(0, initialColorsLimit);

                    // add any cut off colors with same freq as last
                    var pos = initialColorsLimit, len = sorted.length;
                    while (pos < len && this._histogram[sorted[pos]] == freq)
                        idxi32.push(sorted[pos++]);

                    // inject min huegroup colors
                    this._hueStats.inject(idxi32);
                    break;

                case 2:
                    var idxi32 = sorted;
                    break;
            }

            // int32-ify values
            return idxi32.map(function (v) {
                return +v;
            });
        }

        // global top-population
        private _colorStats1D(pointBuffer:Utils.PointContainer) {
            var histG = this._histogram,
                pointArray = pointBuffer.getPointArray(),
                len = pointArray.length;

            for (var i = 0; i < len; i++) {
                var col = pointArray[i].uint32;

                // collect hue stats
                this._hueStats.check(col);

                if (col in histG)
                    histG[col]++;
                else
                    histG[col] = 1;
            }
        }

        // population threshold within subregions
        // FIXME: this can over-reduce (few/no colors same?), need a way to keep
        // important colors that dont ever reach local thresholds (gradients?)
        private _colorStats2D(pointBuffer:Utils.PointContainer) {
            var width = pointBuffer.getWidth(),
                height = pointBuffer.getHeight(),
                pointArray = pointBuffer.getPointArray();

            var boxW = ColorHistogram._boxSize[0],
                boxH = ColorHistogram._boxSize[1],
                area = boxW * boxH,
                boxes = this._makeBoxes(width, height, boxW, boxH),
                histG = this._histogram;

            boxes.forEach(function (box) {
                var effc = Math.round((box.w * box.h) / area) * ColorHistogram._boxPixels,
                    histL = {},
                    col;

                if (effc < 2) effc = 2;

                this._iterateBox(box, width, function (i) {
                    col = pointArray[i].uint32;

                    // collect hue stats
                    this._hueStats.check(col);

                    if (col in histG)
                        histG[col]++;
                    else if (col in histL) {
                        if (++histL[col] >= effc)
                            histG[col] = histL[col];
                    }
                    else
                        histL[col] = 1;
                });
            }, this);

            // inject min huegroup colors
            this._hueStats.inject(histG);

        }

        // iterates @bbox within a parent rect of width @wid; calls @fn, passing index within parent
        private _iterateBox(bbox, wid, fn) {
            var b = bbox,
                i0 = b.y * wid + b.x,
                i1 = (b.y + b.h - 1) * wid + (b.x + b.w - 1),
                cnt = 0, incr = wid - b.w + 1, i = i0;

            do {
                fn.call(this, i);
                i += (++cnt % b.w == 0) ? incr : 1;
            } while (i <= i1);
        }

        /**
         *    partitions a rectangle of width x height into
         *    array of boxes stepX x stepY (or less)
         */
        private _makeBoxes(width, height, stepX, stepY) {
            var wrem = width % stepX,
                hrem = height % stepY,
                xend = width - wrem,
                yend = height - hrem;

            var boxesArray = [];
            for (var y = 0; y < height; y += stepY)
                for (var x = 0; x < width; x += stepX)
                    boxesArray.push({x: x, y: y, w: (x == xend ? wrem : stepX), h: (y == yend ? hrem : stepY)});

            return boxesArray;
        }


    }
}
