/*
 * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint regexp: true */

define(function (require, exports, module) {
    "use strict";

    var KeyEvent           = brackets.getModule("utils/KeyEvent"),
        PreferencesManager = brackets.getModule("preferences/PreferencesManager"),
        StringUtils        = brackets.getModule("utils/StringUtils"),
        Strings            = brackets.getModule("strings"),
        Mustache           = brackets.getModule("thirdparty/mustache/mustache"),
        tinycolor          = require("thirdparty/tinycolor-min");

    /** Mustache template that forms the bare DOM structure of the UI */
    var ColorEditorTemplate = require("text!ColorEditorTemplate.html");

    /**
     * @const @type {number}
     */
    var STEP_MULTIPLIER = 5;

    /**
     * Convert 0x notation into hex6 format for tinycolor
     * compatibility: ("0xFFAACC" => "#FFFFFF")
     */
    function color0xToHex(color,toStr){
        var format0xToHexColor = color.replace("0x","#");
        var hexColor = tinycolor(format0xToHexColor);
        hexColor._format = "0x";

        if(toStr !== null){
            hexColor.toString();
        }
        return hexColor;
    }

    function as0xString(color){
        return color.toHexString().replace("#","0x");
    }

    function checkSetFormat(color,toStr){
        if((/^0x/).test(color)){
            var colorRes = color0xToHex(color,toStr);
            return colorRes;
        }else{
            return tinycolor(color);
        }
    }

    /**
     * Color picker control; may be used standalone or within an InlineColorEditor inline widget.
     * @param {!jQuery} $parent  DOM node into which to append the root of the color picker UI
     * @param {!string} color  Initially selected color
     * @param {!function(string)} callback  Called whenever selected color changes
     * @param {!Array.<{value:string, count:number}>} swatches  Quick-access color swatches to include in UI
     */
    function ColorEditor($parent, color, callback, swatches) {
        // Create the DOM structure, filling in localized strings via Mustache
        this.$element = $(Mustache.render(ColorEditorTemplate, Strings));
        $parent.append(this.$element);

        this._callback = callback;

        this._handleKeydown = this._handleKeydown.bind(this);
        this._handleOpacityKeydown = this._handleOpacityKeydown.bind(this);
        this._handleHslKeydown = this._handleHslKeydown.bind(this);
        this._handleHueKeydown = this._handleHueKeydown.bind(this);
        this._handleSelectionKeydown = this._handleSelectionKeydown.bind(this);
        this._handleOpacityDrag = this._handleOpacityDrag.bind(this);
        this._handleHueDrag = this._handleHueDrag.bind(this);
        this._handleSelectionFieldDrag = this._handleSelectionFieldDrag.bind(this);

        this._originalColor = color;
        this._color = checkSetFormat(color);

        this._redoColor = null;
        this._isUpperCase = PreferencesManager.get("uppercaseColors");
        PreferencesManager.on("change", "uppercaseColors", function () {
            this._isUpperCase = PreferencesManager.get("uppercaseColors");
        }.bind(this));

        this.$colorValue = this.$element.find(".color-value");
        this.$buttonList = this.$element.find("ul.button-bar");
        this.$rgbaButton = this.$element.find(".rgba");
        this.$hexButton = this.$element.find(".hex");
        this.$hslButton = this.$element.find(".hsla");
        this.$0xButton = this.$element.find(".0x");
        this.$currentColor = this.$element.find(".current-color");
        this.$originalColor = this.$element.find(".original-color");
        this.$selection = this.$element.find(".color-selection-field");
        this.$selectionBase = this.$element.find(".color-selection-field .selector-base");
        this.$hueBase = this.$element.find(".hue-slider .selector-base");
        this.$opacityGradient = this.$element.find(".opacity-gradient");
        this.$hueSlider = this.$element.find(".hue-slider");
        this.$hueSelector = this.$element.find(".hue-slider .selector-base");
        this.$opacitySlider = this.$element.find(".opacity-slider");
        this.$opacitySelector = this.$element.find(".opacity-slider .selector-base");
        this.$swatches = this.$element.find(".swatches");

        // Create quick-access color swatches
        this._addSwatches(swatches);

        // Attach event listeners to main UI elements
        this._addListeners();

        this._commitColor(color);
        this.$originalColor.css("background-color", checkSetFormat(this._originalColor));
    }

    /**
     * A string or tinycolor object representing the currently selected color
     * TODO (#2201): type is unpredictable
     * @type {tinycolor|string}
     */
    ColorEditor.prototype._color = null;

    /**
     * An HSV representation of the currently selected color.
     * TODO (#2201): type of _hsv.s/.v is unpredictable
     * @type {!{h:number, s:number|string, v:number|string, a:number}}
     */
    ColorEditor.prototype._hsv = tinycolor("rgba(0,0,0,1)").toHsv();

    /**
     * Color that was selected before undo(), if undo was the last change made. Else null.
     * @type {?string}
     */
    ColorEditor.prototype._redoColor = null;

    /**
     * Initial value the color picker was opened with
     * @type {!string}
     */
    ColorEditor.prototype._originalColor = null;

    /** Returns the root DOM node of the ColorPicker UI */
    ColorEditor.prototype.getRootElement = function () {
        return this.$element;
    };

    /** Attach event listeners for main UI elements */
    ColorEditor.prototype._addListeners = function () {
        this._bindColorFormatToRadioButton("rgba");
        this._bindColorFormatToRadioButton("hex");
        this._bindColorFormatToRadioButton("hsla");
        this._bindColorFormatToRadioButton("0x");

        this._bindInputHandlers();

        this._bindOriginalColorButton();

        this._registerDragHandler(this.$selection, this._handleSelectionFieldDrag);
        this._registerDragHandler(this.$hueSlider, this._handleHueDrag);
        this._registerDragHandler(this.$opacitySlider, this._handleOpacityDrag);
        this._bindKeyHandler(this.$selectionBase, this._handleSelectionKeydown);
        this._bindKeyHandler(this.$hueBase, this._handleHueKeydown);
        this._bindKeyHandler(this.$opacitySelector, this._handleOpacityKeydown);
        this._bindKeyHandler(this.$hslButton, this._handleHslKeydown);

        // General key handler gets bubbling events from any focusable part of widget
        this._bindKeyHandler(this.$element, this._handleKeydown);
    };

    /**
     * Update all UI elements to reflect the selected color (_color and _hsv). It is usually
     * incorrect to call this directly; use _commitColor() or setColorAsHsv() instead.
     */

    ColorEditor.prototype._synchronize = function () {
        var colorValue  = this.getColor().getOriginalInput();
        var colorObject = checkSetFormat(colorValue);
        var hueColor    = "hsl(" + this._hsv.h + ", 100%, 50%)";
        this._updateColorTypeRadioButtons(colorObject.getFormat());
        this.$colorValue.val(colorValue);
        this.$currentColor.css("background-color", checkSetFormat(colorValue,"toStr"));
        this.$selection.css("background-color", hueColor);
        this.$hueBase.css("background-color", hueColor);

        // Update gradients in color square & opacity slider
        this.$selectionBase.css("background-color", colorObject.toHexString());
        this.$opacityGradient.css("background-image", "linear-gradient(" + hueColor + ", transparent)");

        // Update slider thumb positions
        this.$hueSelector.css("bottom", (this._hsv.h / 360 * 100) + "%");
        this.$opacitySelector.css("bottom", (this._hsv.a * 100) + "%");
        if (!isNaN(this._hsv.s)) {      // TODO (#2201): type of _hsv.s/.v is unpredictable
            this._hsv.s = (this._hsv.s * 100) + "%";
        }
        if (!isNaN(this._hsv.v)) {
            this._hsv.v = (this._hsv.v * 100) + "%";
        }
        this.$selectionBase.css({
            left: this._hsv.s,
            bottom: this._hsv.v
        });
    };

    /**
     * Focus the main color square's thumb.
     * @return {boolean} True if we focused the square, false otherwise.
     */
    ColorEditor.prototype.focus = function () {
        if (!this.$selectionBase.is(":focus")) {
            this.$selectionBase.focus();
            return true;
        }
        return false;
    };

    /**
     * Remove any preference listeners before destroying the editor.
     */
    ColorEditor.prototype.destroy = function () {
        PreferencesManager.off("change", "uppercaseColors");
    };

    /**
     * @return {tinycolor|string} The currently selected color (TODO (#2201): type is unpredictable).
     */
    ColorEditor.prototype.getColor = function () {
        return this._color;
    };

    /** Update the format button bar's selection */
    ColorEditor.prototype._updateColorTypeRadioButtons = function (format) {
        this.$buttonList.find("li").removeClass("selected");
        switch (format) {
        case "rgb":
            this.$buttonList.find(".rgba").parent().addClass("selected");
            break;
        case "hex":
        case "name":
            this.$buttonList.find(".hex").parent().addClass("selected");
            break;
        case "hsl":
            this.$buttonList.find(".hsla").parent().addClass("selected");
            break;
        case "0x":
            this.$buttonList.find(".0x").parent().addClass("selected");
            break;
        }
    };

    /** Add event listeners to the format button bar */
    ColorEditor.prototype._bindColorFormatToRadioButton = function (buttonClass, propertyName, value) {
        var handler,
            self = this;
        handler = function (event) {
            var newFormat   = $(event.currentTarget).html().toLowerCase().replace("%", "p"),
                newColor    = self.getColor().toString();

            var colorObject = checkSetFormat(newColor);

            switch (newFormat) {
            case "hsla":
                newColor = colorObject.toHslString();
                break;
            case "rgba":
                newColor = colorObject.toRgbString();
                break;
            case "prgba":
                newColor = colorObject.toPercentageRgbString();
                break;
            case "hex":
                newColor = colorObject.toHexString();
                self._hsv.a = 1;
                break;
            case "0x":
                newColor = as0xString(colorObject);
                self._hsv.a = 1;
                self._format = "0x";
                break;
            }

            // We need to run this again whenever RGB/HSL/Hex conversions
            // are performed to preserve the case
            newColor = self._isUpperCase ? newColor.toUpperCase() : newColor;
            self._commitColor(newColor, false);
        };
        this.$element.find("." + buttonClass).click(handler);
    };

    /** Add event listener to the "original color value" swatch */
    ColorEditor.prototype._bindOriginalColorButton = function () {
        var self = this;
        this.$originalColor.click(function (event) {
            self._commitColor(self._originalColor, true);
        });
    };

    /**
     * Convert percentage values in an RGB color into normal RGB values in the range of 0 - 255.
     * If the original color is already in non-percentage format, does nothing.
     * @param {string} color The color to be converted to non-percentage RGB color string.
     * @return {string} an RGB color string in the normal format using non-percentage values
     */
    ColorEditor.prototype._convertToNormalRGB = function (color) {
        var matches = color.match(/^rgb.*?([0-9]+)\%.*?([0-9]+)\%.*?([0-9]+)\%/i);
        if (matches) {
            var i, percentStr, value;
            for (i = 0; i < 3; i++) {
                percentStr = matches[i + 1];
                value = Math.round(255 * Number(percentStr) / 100);
                if (!isNaN(value)) {
                    color = color.replace(percentStr + "%", value);
                }
            }
        }
        return color;
    };

    /**
     * Normalize the given color string into the format used by tinycolor, by adding a space
     * after commas.
     * @param {string} color The color to be corrected if it looks like an RGB or HSL color.
     * @return {string} a normalized color string.
     */
    ColorEditor.prototype._normalizeColorString = function (color) {
        var normalizedColor = color;

        // Convert 6-digit hex to 3-digit hex as TinyColor (#ffaacc -> #fac)
        if (color.match(/^#[0-9a-fA-F]{6}/)) {
            return tinycolor(color).toString();
        }
        if (color.match(/^(rgb|hsl)/i)) {
            normalizedColor = normalizedColor.replace(/,\s*/g, ", ");
            normalizedColor = normalizedColor.replace(/\(\s+/, "(");
            normalizedColor = normalizedColor.replace(/\s+\)/, ")");
        }
        return normalizedColor;
    };

    /** Handle changes in text field */
    ColorEditor.prototype._handleTextFieldInput = function (losingFocus) {
        var newColor    = $.trim(this.$colorValue.val()),
            newColorObj = checkSetFormat(newColor),
            newColorOk  = newColorObj.isValid();

        // TinyColor will auto correct an incomplete rgb or hsl value into a valid color value.
        // eg. rgb(0,0,0 -> rgb(0, 0, 0)
        // We want to avoid having TinyColor do this, because we don't want to sync the color
        // to the UI if it's incomplete. To accomplish this, we first normalize the original
        // color string into the format TinyColor would generate, and then compare it to what
        // TinyColor actually generates to see if it's different. If so, then we assume the color
        // was incomplete to begin with.
        if (newColorOk) {
            var colorStr = newColor.replace("0x","#") || newColor;
            newColorOk = (newColorObj.toString() === this._normalizeColorString(colorStr));
        }

        // Restore to the previous valid color if the new color is invalid or incomplete.
        if (losingFocus && !newColorOk) {
            newColor = this.getColor().toString();
        }

        // Sync only if we have a valid color or we're restoring the previous valid color.
        if (losingFocus || newColorOk) {
            this._commitColor(newColor, true);
        }
    };

    ColorEditor.prototype._bindInputHandlers = function () {
        var self = this;

        this.$colorValue.bind("input", function (event) {
            self._handleTextFieldInput(false);
        });

        this.$colorValue.bind("change", function (event) {
            self._handleTextFieldInput(true);
        });
    };

    /**
     * Populate the UI with the given color swatches and add listeners so they're selectable.
     * @param {!Array.<{value:string, count:number}>} swatches
     */
    ColorEditor.prototype._addSwatches = function (swatches) {
        var self = this;

        // Create swatches
        swatches.forEach(function (swatch) {
            var swatchValue = swatch.value.replace("0x","#") || swatch.value;
            var stringFormat = (swatch.count > 1) ? Strings.COLOR_EDITOR_USED_COLOR_TIP_PLURAL : Strings.COLOR_EDITOR_USED_COLOR_TIP_SINGULAR,
                usedColorTip = StringUtils.format(stringFormat, swatch.value, swatch.count);

            self.$swatches.append("

 " + swatch.value + "
");
        });

        // Add key & click listeners to each
        this.$swatches.find("li").keydown(function (event) {
            if (event.keyCode === KeyEvent.DOM_VK_RETURN ||
                    event.keyCode === KeyEvent.DOM_VK_ENTER ||
                    event.keyCode === KeyEvent.DOM_VK_SPACE) {
                // Enter/Space is same as clicking on swatch

                self._commitColor($(event.currentTarget).find(".value").html());
            } else if (event.keyCode === KeyEvent.DOM_VK_TAB) {
                // Tab on last swatch loops back to color square
                if (!event.shiftKey && $(this).next("li").length === 0) {
                    self.$selectionBase.focus();
                    return false;
                }
            }
        });

        this.$swatches.find("li").click(function (event) {
            self._commitColor($(event.currentTarget).find(".value").html());
        });
    };

    /**
     * Checks whether colorVal is a valid color
     * @param {!string} colorVal
     * @return {boolean} Whether colorVal is valid
     */
    ColorEditor.prototype.isValidColor = function (colorVal) {
        return tinycolor(colorVal).isValid();
    };

    /**
     * Sets _hsv and _color based on an HSV input, and updates the UI. Attempts to preserve
     * the previous color format.
     * @param {!{h:number=, s:number=, v:number=}} hsv  Any missing values use the previous color's values.
     */
    ColorEditor.prototype.setColorAsHsv = function (hsv) {
        var colorVal, newColor,
            oldFormat = tinycolor(this.getColor()).getFormat();

        // Set our state to the new color
        $.extend(this._hsv, hsv);
        newColor = tinycolor(this._hsv);

        switch (oldFormat) {
        case "hsl":
            colorVal = newColor.toHslString();
            break;
        case "rgb":
            colorVal = newColor.toRgbString();
            break;
        case "prgb":
            colorVal = newColor.toPercentageRgbString();
            break;
        case "hex":
        case "name":
            colorVal = this._hsv.a < 1 ? newColor.toRgbString() : newColor.toHexString();
            break;
        case "0x":
            colorVal = as0xString(newColor);
            break;
        }
        colorVal = this._isUpperCase ? colorVal.toUpperCase() : colorVal;
        this._commitColor(colorVal, false);
    };

    /**
     * Sets _color (and optionally _hsv) based on a string input, and updates the UI. The string's
     * format determines the new selected color's format.
     * @param {!string} colorVal
     * @param {boolean=} resetHsv  Pass false ONLY if hsv set already been modified to match colorVal. Default: true.
     */
    ColorEditor.prototype._commitColor = function (colorVal, resetHsv) {

        if (resetHsv === undefined) {
            resetHsv = true;
        }
        this._callback(colorVal);

        var colorObj = checkSetFormat(colorVal);
        colorObj._originalInput = colorVal;
        this._color = colorObj;

        if (resetHsv) {
            this._hsv = this._color.toHsv();
        }

        this._redoColor = null;  // if we had undone, this new value blows away the redo history
        this._synchronize();
    };

    /**
     * Sets _color and _hsv based on a string input, and updates the UI. The string's
     * format determines the new selected color's format.
     * @param {!string} colorVal
     */
    ColorEditor.prototype.setColorFromString = function (colorVal) {
        this._commitColor(colorVal, true);  // TODO (#2204): make this less entangled with setColorAsHsv()
    };

    /** Converts a mouse coordinate to be relative to zeroPos, and clips to [0, maxOffset] */
    function _getNewOffset(pos, zeroPos, maxOffset) {
        var offset = pos - zeroPos;
        offset = Math.min(maxOffset, Math.max(0, offset));
        return offset;
    }

    /** Dragging color square's thumb */
    ColorEditor.prototype._handleSelectionFieldDrag = function (event) {
        var height  = this.$selection.height(),
            width   = this.$selection.width(),
            xOffset = _getNewOffset(event.clientX, this.$selection.offset().left, width),
            yOffset = _getNewOffset(event.clientY, this.$selection.offset().top, height),
            hsv     = {};
        hsv.s = xOffset / width;
        hsv.v = 1 - yOffset / height;
        this.setColorAsHsv(hsv, false);
        if (!this.$selection.find(".selector-base").is(":focus")) {
            this.$selection.find(".selector-base").focus();
        }
    };

    /** Dragging hue slider thumb */
    ColorEditor.prototype._handleHueDrag = function (event) {
        var height = this.$hueSlider.height(),
            offset = _getNewOffset(event.clientY, this.$hueSlider.offset().top, height),
            hsv    = {};
        hsv.h = (1 - offset / height) * 360;
        this.setColorAsHsv(hsv, false);
        if (!this.$hueSlider.find(".selector-base").is(":focus")) {
            this.$hueSlider.find(".selector-base").focus();
        }
    };

    /** Dragging opacity slider thumb */
    ColorEditor.prototype._handleOpacityDrag = function (event) {
        var height = this.$opacitySlider.height(),
            offset = _getNewOffset(event.clientY, this.$opacitySlider.offset().top, height),
            hsv    = {};
        hsv.a = 1 - (offset / height);
        this.setColorAsHsv(hsv, false);
        if (!this.$opacitySlider.find(".selector-base").is(":focus")) {
            this.$opacitySlider.find(".selector-base").focus();
        }
    };

    /**
     * Helper for attaching drag-related mouse listeners to an element. It's up to
     * 'handler' to actually move the element as mouse is dragged.
     * @param {!function(jQuery.event)} handler  Called whenever drag position changes
     */
    ColorEditor.prototype._registerDragHandler = function ($element, handler) {
        var mouseupHandler = function (event) {
            $(window).unbind("mousemove", handler);
            $(window).unbind("mouseup", mouseupHandler);
        };
        $element.mousedown(function (event) {
            $(window).bind("mousemove", handler);
            $(window).bind("mouseup", mouseupHandler);
        });
        $element.mousedown(handler);  // run drag-update handler on initial mousedown too
    };

    /**
     * Handles undo gestures while color picker has focus. We don't want to let CodeMirror's
     * usual undo logic run since it will destroy our marker.
     */
    ColorEditor.prototype.undo = function () {
        if (this._originalColor.toString() !== this._color.toString()) {
            var curColor = this._color.toString();
            this._commitColor(this._originalColor, true);
            this._redoColor = curColor;
        }
    };

    /** Similarly, handle redo gestures while color picker has focus. */
    ColorEditor.prototype.redo = function () {
        if (this._redoColor) {
            this._commitColor(this._redoColor, true);
            this._redoColor = null;
        }
    };

    /**
     * Global handler for keys in the color editor. Catches undo/redo keys and traps
     * arrow keys that would be handled by the scroller.
     */
    ColorEditor.prototype._handleKeydown = function (event) {
        var hasCtrl = (brackets.platform === "win") ? (event.ctrlKey) : (event.metaKey);
        if (hasCtrl) {
            switch (event.keyCode) {
            case KeyEvent.DOM_VK_Z:
                if (event.shiftKey) {
                    this.redo();
                } else {
                    this.undo();
                }
                return false;
            case KeyEvent.DOM_VK_Y:
                this.redo();
                return false;
            }
        } else {
            if (event.keyCode === KeyEvent.DOM_VK_LEFT ||
                    event.keyCode === KeyEvent.DOM_VK_RIGHT ||
                    event.keyCode === KeyEvent.DOM_VK_UP ||
                    event.keyCode === KeyEvent.DOM_VK_DOWN) {
                // Prevent arrow keys that weren't handled by a child control
                // from being handled by a parent, either through bubbling or
                // through default native behavior. There isn't a good general
                // way to tell if the target would handle this event by default,
                // so we look to see if the target is a text input control.
                var preventDefault = false,
                    $target = $(event.target);

                // If the input has no "type" attribute, it defaults to text. So we
                // have to check for both possibilities.
                if ($target.is("input:not([type])") || $target.is("input[type=text]")) {
                    // Text input control. In WebKit, if the cursor gets to the start
                    // or end of a text field and can't move any further, the default
                    // action doesn't take place in the text field, so the event is handled
                    // by the outer scroller. We have to prevent in that case too.
                    if ($target[0].selectionStart === $target[0].selectionEnd &&
                            ((event.keyCode === KeyEvent.DOM_VK_LEFT && $target[0].selectionStart === 0) ||
                             (event.keyCode === KeyEvent.DOM_VK_RIGHT && $target[0].selectionEnd === $target.val().length))) {
                        preventDefault = true;
                    }
                } else {
                    // Not a text input control, so we want to prevent default.
                    preventDefault = true;
                }

                if (preventDefault) {
                    event.stopPropagation();
                    return false; // equivalent to event.preventDefault()
                }
            }
        }
    };

    ColorEditor.prototype._handleHslKeydown = function (event) {
        if (event.keyCode === KeyEvent.DOM_VK_TAB) {
            // If we're the last focusable element (no color swatches), Tab wraps around to color square
            if (!event.shiftKey) {
                if (this.$swatches.children().length === 0) {
                    this.$selectionBase.focus();
                    return false;
                }
            }
        }
    };

    /** Key events on the color square's thumb */
    ColorEditor.prototype._handleSelectionKeydown = function (event) {
        var hsv = {},
            step = 1.5,
            xOffset,
            yOffset,
            adjustedOffset;

        switch (event.keyCode) {
        case KeyEvent.DOM_VK_LEFT:
        case KeyEvent.DOM_VK_RIGHT:
            step = event.shiftKey ? step * STEP_MULTIPLIER : step;
            xOffset = Number($.trim(this.$selectionBase[0].style.left.replace("%", "")));
            adjustedOffset = (event.keyCode === KeyEvent.DOM_VK_LEFT) ? (xOffset - step) : (xOffset + step);
            xOffset = Math.min(100, Math.max(0, adjustedOffset));
            hsv.s = xOffset / 100;
            this.setColorAsHsv(hsv, false);
            return false;
        case KeyEvent.DOM_VK_DOWN:
        case KeyEvent.DOM_VK_UP:
            step = event.shiftKey ? step * STEP_MULTIPLIER : step;
            yOffset = Number($.trim(this.$selectionBase[0].style.bottom.replace("%", "")));
            adjustedOffset = (event.keyCode === KeyEvent.DOM_VK_DOWN) ? (yOffset - step) : (yOffset + step);
            yOffset = Math.min(100, Math.max(0, adjustedOffset));
            hsv.v = yOffset / 100;
            this.setColorAsHsv(hsv, false);
            return false;
        case KeyEvent.DOM_VK_TAB:
            // Shift+Tab loops back to last focusable element: last swatch if any; format button bar if not
            if (event.shiftKey) {
                if (this.$swatches.children().length === 0) {
                    this.$hslButton.focus();
                } else {
                    this.$swatches.find("li:last").focus();
                }
                return false;
            }
            break;
        }
    };

    /** Key events on the hue slider thumb */
    ColorEditor.prototype._handleHueKeydown = function (event) {
        var hsv = {},
            hue = Number(this._hsv.h),
            step = 3.6;

        switch (event.keyCode) {
        case KeyEvent.DOM_VK_DOWN:
            step = event.shiftKey ? step * STEP_MULTIPLIER : step;
            hsv.h = (hue - step) <= 0 ? 360 - step : hue - step;
            this.setColorAsHsv(hsv, false);
            return false;
        case KeyEvent.DOM_VK_UP:
            step = event.shiftKey ? step * STEP_MULTIPLIER : step;
            hsv.h = (hue + step) >= 360 ? step : hue + step;
            this.setColorAsHsv(hsv, false);
            return false;
        }
    };

    /** Key events on the opacity slider thumb */
    ColorEditor.prototype._handleOpacityKeydown = function (event) {
        var alpha = this._hsv.a,
            hsv = {},
            step = 0.01;

        switch (event.keyCode) {
        case KeyEvent.DOM_VK_DOWN:
            step = event.shiftKey ? step * STEP_MULTIPLIER : step;
            if (alpha > 0) {
                hsv.a = (alpha - step) <= 0 ? 0 : alpha - step;
                this.setColorAsHsv(hsv);
            }
            return false;
        case KeyEvent.DOM_VK_UP:
            step = event.shiftKey ? step * STEP_MULTIPLIER : step;
            if (alpha < 100) {
                hsv.a = (alpha + step) >= 1 ? 1 : alpha + step;
                this.setColorAsHsv(hsv);
            }
            return false;
        }
    };

    ColorEditor.prototype._bindKeyHandler = function ($element, handler) {
        $element.bind("keydown", handler);
    };

    // Prevent clicks on some UI elements (color selection field, slider and large swatch) from taking focus
    $(window.document).on("mousedown", ".color-selection-field, .slider, .large-swatch", function (e) {
        e.preventDefault();
    });

    exports.ColorEditor = ColorEditor;
});


keep keep keep keep keep replace keep keep keep

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, $ */
define(function () {
    'use strict';
    return {
        color: "black",
        size: 10,
        material: function () {
            return "cotton";
        }
    };
});


/*
 * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*unittests: StringMatch */

define(function (require, exports, module) {
    "use strict";

    var _ = require("thirdparty/lodash");

    /*
     * Performs matching that is useful for QuickOpen and similar searches.
     */

    /** Object representing a search result with associated metadata (added as extra ad hoc fields) */
    function SearchResult(label) {
        this.label = label;
    }

    /*
     * Identifies the "special" characters in the given string.
     * Special characters for matching purposes are:
     *
     * * the first character
     * * "/" and the character following the "/"
     * * "_", "." and "-" and the character following it
     * * an uppercase character that follows a lowercase one (think camelCase)
     *
     * The returned object contains an array called "specials". This array is
     * a list of indexes into the original string where all of the special
     * characters are. It also has a property "lastSegmentSpecialsIndex" which
     * is an index into the specials array that denotes which location is the
     * beginning of the last path segment. (This is used to allow scanning of
     * the last segment's specials separately.)
     *
     * @param {string} input string to break apart (e.g. filename that is being searched)
     * @return {{specials:Array.<number>, lastSegmentSpecialsIndex:number}}
     */
    function findSpecialCharacters(str) {
        var i, c;

        // the beginning of the string is always special
        var specials = [0];

        // lastSegmentSpecialsIndex starts off with the assumption that
        // there are no segments
        var lastSegmentSpecialsIndex = 0;

        // used to track down the camelCase changeovers
        var lastWasLowerCase = false;

        for (i = 0; i < str.length; i++) {
            c = str[i];
            if (c === "/") {
                // new segment means this character and the next are special
                specials.push(i++);
                specials.push(i);
                lastSegmentSpecialsIndex = specials.length - 1;
                lastWasLowerCase = false;
            } else if (c === "." || c === "-" || c === "_") {
                // _, . and - are separators so they are
                // special and so is the next character
                specials.push(i);
                if (str[i + 1] !== "/") {
                    // if the next key is a slash, handle it separately
                    // see #10871
                    specials.push(++i);
                }
                lastWasLowerCase = false;
            } else if (c.toUpperCase() === c) {
                // this is the check for camelCase changeovers
                if (lastWasLowerCase) {
                    specials.push(i);
                }
                lastWasLowerCase = false;
            } else {
                lastWasLowerCase = true;
            }
        }
        return {
            specials: specials,
            lastSegmentSpecialsIndex: lastSegmentSpecialsIndex
        };
    }

    // states used during the scanning of the string
    var SPECIALS_MATCH = 0;
    var ANY_MATCH = 1;

    // Scores can be hard to make sense of. The DEBUG_SCORES flag
    // provides a way to peek into the parts that made up a score.
    // This flag is used for manual debugging and in the unit tests only.
    var DEBUG_SCORES = false;
    function _setDebugScores(ds) {
        DEBUG_SCORES = ds;
    }

    // Constants for scoring
    var SPECIAL_POINTS = 40;
    var MATCH_POINTS = 10;
    var UPPER_CASE_MATCH = 100;
    var CONSECUTIVE_MATCHES_POINTS = 8;
    var BEGINNING_OF_NAME_POINTS = 13;
    var LAST_SEGMENT_BOOST = 1;
    var DEDUCTION_FOR_LENGTH = 0.2;
    var NOT_STARTING_ON_SPECIAL_PENALTY = 25;

    // Used in match lists to designate matches of "special" characters (see
    // findSpecialCharacters above
    function SpecialMatch(index, upper) {
        this.index = index;
        if (upper) {
            this.upper = upper;
        }
    }

    // Used in match lists to designate any matched characters that are not special
    function NormalMatch(index, upper) {
        this.index = index;
        if (upper) {
            this.upper = upper;
        }
    }

    /*
     * Finds the best matches between the query and the string. The query is
     * compared with str (usually a lower case string with a lower case
     * query).
     *
     * Generally speaking, this function tries to find "special" characters
     * (see findSpecialCharacters above) first. Failing that, it starts scanning
     * the "normal" characters. Sometimes, it will find a special character that matches
     * but then not be able to match the rest of the query. In cases like that, the
     * search will backtrack and try to find matches for the whole query earlier in the
     * string.
     *
     * A contrived example will help illustrate how the searching and backtracking works. It's a bit long,
     * but it illustrates different pieces of the algorithm which can be tricky. Let's say that we're
     * searching the string "AzzBzzCzdzezzDgxgEF" for "abcdex".
     *
     * To start with, it will match "abcde" from the query to "A B C D E" in the string (the spaces
     * represent gaps in the matched part of the string), because those are all "special characters".
     * However, the "x" in the query doesn't match the "F" which is the only character left in the
     * string.
     *
     * Backtracking kicks in. The "E" is pulled off of the match list.
     * deadBranches[4] is set to the "g" before the "E". This means that for the 5th
     * query character (the "e") we know that we don't have a match beyond that point in the string.
     *
     * To resume searching, the backtrack function looks at the previous match (the "D") and starts
     * searching in character-by-character (ANY_MATCH) mode right after that. It fails to find an
     * "e" before it gets to deadBranches[4], so it has to backtrack again.
     *
     * This time, the "D" is pulled off the match list.
     * deadBranches[3] is set to the "z" before the "D", because we know that for the "dex" part of the
     * query, we can't make it work past the "D". We'll resume searching with the "z" after the "C".
     *
     * Doing an ANY_MATCH search, we find the "d". We then start searching specials for "e", but we
     * stop before we get to "E" because deadBranches[4] tells us that's a dead end. So, we switch
     * to ANY_MATCH and find the "e".
     *
     * Finally, we search for the "x". We don't find a special that matches, so we start an ANY_MATCH
     * search. Then we find the "x", and we have a successful match.
     *
     * Here are some notes on how the algorithm works:
     *
     * * We only backtrack() when we're exhausted both special AND normal forward searches past that point,
     *   for the query remainder we currently have.  For a different query remainder, we may well get further
     *   along - hence deadBranches[] being dependent on queryCounter; but in order to get a different query
     *   remainder, we must give up one or more current matches by backtracking.
     *
     * * Normal "any char" forward search is a superset of special matching mode -- anything that would have
     *   been matched in special mode *could* also be matched by normal mode. In practice, however,
     *   any special characters that could have matched would be picked up first by the specials matching
     *   code.
     *
     * * backtrack() always goes at least as far back as str[deadBranches[queryCounter]-1] before allowing
     *   forward searching to resume
     *
     * * When `deadBranches[queryCounter] = strCounter` it means if we're still trying to match
     *   `queryLower[queryCounter]` and we get to `str[strCounter]`, there's no way we can match the
     *   remainer of `queryLower` with the remainder of `str` -- either using specials-only or
     *   full any-char matching.
     *
     * * We know this because deadBranches[] is set in backtrack(), and we don't get to backtrack() unless
     *   either:
     *   1. We've already exhausted both special AND normal forward searches past that point
     *      (i.e. backtrack() due to `strCounter >= str.length`, yet `queryCounter < query.length`)
     *   2. We stopped searching further forward due to a previously set deadBranches[] value
     *      (i.e. backtrack() due to `strCounter > deadBranches[queryCounter]`, yet
     *      `queryCounter < query.length`)
     *
     * @param {string} query the search string (generally lower cased)
     * @param {string} str the string to compare with (generally lower cased)
     * @param {string} originalQuery the "non-normalized" query string (used to detect case match priority)
     * @param {string} originalStr the "non-normalized" string to compare with (used to detect case match priority)
     * @param {Array} specials list of special indexes in str (from findSpecialCharacters)
     * @param {int} startingSpecial index into specials array to start scanning with
     * @return {Array.<SpecialMatch|NormalMatch>} matched indexes or null if no matches possible
     */
    function _generateMatchList(query, str, originalQuery, originalStr, specials, startingSpecial) {
        var result = [];

        // used to keep track of which special character we're testing now
        var specialsCounter = startingSpecial;

        // strCounter and queryCounter are the indexes used for pulling characters
        // off of the str/compareLower and query.
        var strCounter = specials[startingSpecial];
        var queryCounter;

        // the search branches out between special characters and normal characters
        // that are found via consecutive character scanning. In the process of
        // performing these scans, we discover that parts of the query will not match
        // beyond a given point in the string. We keep track of that information
        // in deadBranches, which has a slot for each character in the query.
        // The value stored in the slot is the index into the string after which we
        // are certain there is no match.
        var deadBranches = [];

        for (queryCounter = 0; queryCounter < query.length; queryCounter++) {
            deadBranches[queryCounter] = Infinity;
        }

        queryCounter = 0;

        var state = SPECIALS_MATCH;

        // Compares the current character from the query string against the
        // special characters in str. Returns true if a match was found,
        // false otherwise.
        function findMatchingSpecial() {
            // used to loop through the specials
            var i;

            for (i = specialsCounter; i < specials.length; i++) {
                // short circuit this search when we know there are no matches following
                if (specials[i] >= deadBranches[queryCounter]) {
                    break;
                }

                // First, ensure that we're not comparing specials that
                // come earlier in the string than our current search position.
                // This can happen when the string position changes elsewhere.
                if (specials[i] < strCounter) {
                    specialsCounter = i;
                } else if (query[queryCounter] === str[specials[i]]) {
                    // we have a match! do the required tracking
                    strCounter = specials[i];

                    // Upper case match check:
                    // If the query and original string matched, but the original string
                    // and the lower case version did not, that means that the original
                    // was upper case.
                    var upper = originalQuery[queryCounter] === originalStr[strCounter] && originalStr[strCounter] !== str[strCounter];
                    result.push(new SpecialMatch(strCounter, upper));
                    specialsCounter = i;
                    queryCounter++;
                    strCounter++;
                    return true;
                }
            }

            return false;
        }

        // This function implements the backtracking that is done when we fail to find
        // a match with the query using the "search for specials first" approach.
        //
        // returns false when it is not able to backtrack successfully
        function backtrack() {

            // The idea is to pull matches off of our match list, rolling back
            // characters from the query. We pay special attention to the special
            // characters since they are searched first.
            while (result.length > 0) {
                var item = result.pop();

                // nothing in the list? there's no possible match then.
                if (!item) {
                    return false;
                }

                // we pulled off a match, which means that we need to put a character
                // back into our query. strCounter is going to be set once we've pulled
                // off the right special character and know where we're going to restart
                // searching from.
                queryCounter--;

                if (item instanceof SpecialMatch) {
                    // pulled off a special, which means we need to make that special available
                    // for matching again
                    specialsCounter--;

                    // check to see if we've gone back as far as we need to
                    if (item.index < deadBranches[queryCounter]) {
                        // we now know that this part of the query does not match beyond this
                        // point
                        deadBranches[queryCounter] = item.index - 1;

                        // since we failed with the specials along this track, we're
                        // going to reset to looking for matches consecutively.
                        state = ANY_MATCH;

                        // we figure out where to start looking based on the new
                        // last item in the list. If there isn't anything else
                        // in the match list, we'll start over at the starting special
                        // (which is generally the beginning of the string, or the
                        // beginning of the last segment of the string)
                        item = result[result.length - 1];
                        if (!item) {
                            strCounter = specials[startingSpecial] + 1;
                            return true;
                        }
                        strCounter = item.index + 1;
                        return true;
                    }
                }
            }
            return false;
        }

        while (true) {

            // keep looping until we've either exhausted the query or the string
            while (queryCounter < query.length && strCounter < str.length && strCounter <= deadBranches[queryCounter]) {
                if (state === SPECIALS_MATCH) {
                    if (!findMatchingSpecial()) {
                        state = ANY_MATCH;
                    }
                }

                if (state === ANY_MATCH) {
                    // we look character by character for matches
                    if (query[queryCounter] === str[strCounter]) {
                        // got a match! record it, and switch back to searching specials

                        // See the specials section above for a comment on the expression
                        // for `upper` below.
                        var upper = originalQuery[queryCounter] === originalStr[strCounter] && originalStr[strCounter] !== str[strCounter];
                        result.push(new NormalMatch(strCounter++, upper));

                        queryCounter++;
                        state = SPECIALS_MATCH;
                    } else {
                        // no match, keep looking
                        strCounter++;
                    }
                }
            }

            // if we've finished the query, or we haven't finished the query but we have no
            // more backtracking we can do, then we're all done searching.
            if (queryCounter >= query.length || (queryCounter < query.length && !backtrack())) {
                break;
            }
        }

        // return null when we don't find anything
        if (queryCounter < query.length || result.length === 0) {
            return null;
        }
        return result;
    }

    /*
     * Seek out the best match in the last segment (generally the filename).
     * Matches in the filename are preferred, but the query entered could match
     * any part of the path. So, we find the best match we can get in the filename
     * and then allow for searching the rest of the string with any characters that
     * are left from the beginning of the query.
     *
     * The parameters and return value are the same as for getMatchRanges,
     * except this function is always working on the last segment and the
     * result can optionally include a remainder, which is the characters
     * at the beginning of the query which did not match in the last segment.
     *
     * @param {string} query the search string (generally lower cased)
     * @param {string} str the string to compare with (generally lower cased)
     * @param {string} originalQuery the "non-normalized" query string (used to detect case match priority)
     * @param {string} originalStr the "non-normalized" string to compare with (used to detect case match priority)
     * @param {Array} specials list of special indexes in str (from findSpecialCharacters)
     * @param {int} startingSpecial index into specials array to start scanning with
     * @param {boolean} lastSegmentStart which character does the last segment start at
     * @return {{remainder:int, matchList:Array.<SpecialMatch|NormalMatch>}} matched indexes or null if no matches possible
     */
    function _lastSegmentSearch(query, str, originalQuery, originalStr, specials, startingSpecial, lastSegmentStart) {
        var queryCounter, matchList;

        // It's possible that the query is longer than the last segment.
        // If so, we can chop off the bit that we know couldn't possibly be there.
        var remainder = "",
            originalRemainder = "",
            extraCharacters = specials[startingSpecial] + query.length - str.length;

        if (extraCharacters > 0) {
            remainder = query.substring(0, extraCharacters);
            originalRemainder = originalQuery.substring(0, extraCharacters);
            query = query.substring(extraCharacters);
            originalQuery = originalQuery.substring(extraCharacters);
        }

        for (queryCounter = 0; queryCounter < query.length; queryCounter++) {
            matchList = _generateMatchList(query.substring(queryCounter),
                                     str, originalQuery.substring(queryCounter),
                                     originalStr, specials, startingSpecial);

            // if we've got a match *or* there are no segments in this string, we're done
            if (matchList || startingSpecial === 0) {
                break;
            }
        }

        if (queryCounter === query.length || !matchList) {
            return null;
        } else {
            return {
                remainder: remainder + query.substring(0, queryCounter),
                originalRemainder: originalRemainder + originalQuery.substring(0, queryCounter),
                matchList: matchList
            };
        }
    }

    /*
     * Implements the top-level search algorithm. Search the last segment first,
     * then search the rest of the string with the remainder.
     *
     * The parameters and return value are the same as for getMatchRanges.
     *
     * @param {string} queryLower the search string (will be searched lower case)
     * @param {string} compareLower the lower-cased string to search
     * @param {string} originalQuery the "non-normalized" query string (used to detect case match priority)
     * @param {string} originalStr the "non-normalized" string to compare with (used to detect case match priority)
     * @param {Array} specials list of special indexes in str (from findSpecialCharacters)
     * @param {int} lastSegmentSpecialsIndex index into specials array to start scanning with
     * @return {Array.<SpecialMatch|NormalMatch>} matched indexes or null if no matches possible
     */
    function _wholeStringSearch(queryLower, compareLower, originalQuery, originalStr, specials, lastSegmentSpecialsIndex) {
        var lastSegmentStart = specials[lastSegmentSpecialsIndex];
        var result;
        var matchList;

        result = _lastSegmentSearch(queryLower, compareLower, originalQuery, originalStr, specials, lastSegmentSpecialsIndex, lastSegmentStart);

        if (result) {
            matchList = result.matchList;

            // Do we have more query characters that did not fit?
            if (result.remainder) {
                // Scan with the remainder only through the beginning of the last segment
                var remainderMatchList = _generateMatchList(result.remainder,
                                              compareLower.substring(0, lastSegmentStart),
                                              result.originalRemainder,
                                              originalStr.substring(0, lastSegmentStart),
                                              specials.slice(0, lastSegmentSpecialsIndex), 0);

                if (remainderMatchList) {
                    // add the new matched ranges to the beginning of the set of ranges we had
                    matchList.unshift.apply(matchList, remainderMatchList);
                } else {
                    // no match
                    return null;
                }
            }
        } else {
            // No match in the last segment, so we start over searching the whole
            // string
            matchList = _generateMatchList(queryLower, compareLower, originalQuery, originalStr, specials, 0);
        }

        return matchList;
    }

    /**
     * Converts a list of matches into a form suitable for returning from stringMatch.
     *
     * @param {Array.<SpecialMatch|NormalMatch>} matchList to convert
     * @param {string} original string
     * @param {int} character index where last segment begins
     * @return {{ranges:Array.<{text:string, matched:boolean, includesLastSegment:boolean}>, matchGoodness:int, scoreDebug: Object}} matched ranges and score
     */
    function _computeRangesAndScore(matchList, str, lastSegmentStart) {
        var matchCounter;
        var ranges = [];
        var lastMatchIndex = -1;
        var lastSegmentScore = 0;
        var currentRangeStartedOnSpecial = false;

        var score = 0;
        var scoreDebug;
        if (DEBUG_SCORES) {
            scoreDebug = {
                special: 0,
                match: 0,
                upper: 0,
                lastSegment: 0,
                beginning: 0,
                lengthDeduction: 0,
                consecutive: 0,
                notStartingOnSpecial: 0
            };
        }

        var currentRange = null;

        // Records the current range and adds any additional ranges required to
        // get to character index c. This function is called before starting a new range
        // or returning from the function.
        function closeRangeGap(c) {
            // Close the current range
            if (currentRange) {
                currentRange.includesLastSegment = lastMatchIndex >= lastSegmentStart;
                if (currentRange.matched && currentRange.includesLastSegment) {
                    if (DEBUG_SCORES) {
                        scoreDebug.lastSegment += lastSegmentScore * LAST_SEGMENT_BOOST;
                    }
                    score += lastSegmentScore * LAST_SEGMENT_BOOST;
                }

                if (currentRange.matched && !currentRangeStartedOnSpecial) {
                    if (DEBUG_SCORES) {
                        scoreDebug.notStartingOnSpecial -= NOT_STARTING_ON_SPECIAL_PENALTY;
                    }
                    score -= NOT_STARTING_ON_SPECIAL_PENALTY;
                }
                ranges.push(currentRange);
            }

            // If there was space between the new range and the last,
            // add a new unmatched range before the new range can be added.
            if (lastMatchIndex + 1 < c) {
                ranges.push({
                    text: str.substring(lastMatchIndex + 1, c),
                    matched: false,
                    includesLastSegment: c > lastSegmentStart
                });
            }
            currentRange = null;
            lastSegmentScore = 0;
        }

        // In some cases (see the use of this variable below), we accelerate the
        // bonus the more consecutive matches there are.
        var numConsecutive = 0;

        // Adds a matched character to the appropriate range
        function addMatch(match) {
            // Pull off the character index
            var c = match.index;
            var newPoints = 0;

            // A match means that we need to do some scoring bookkeeping.
            // Start with points added for any match
            if (DEBUG_SCORES) {
                scoreDebug.match += MATCH_POINTS;
            }
            newPoints += MATCH_POINTS;

            if (match.upper) {
                if (DEBUG_SCORES) {
                    scoreDebug.upper += UPPER_CASE_MATCH;
                }
                newPoints += UPPER_CASE_MATCH;
            }

            // A bonus is given for characters that match at the beginning
            // of the filename
            if (c === lastSegmentStart) {
                if (DEBUG_SCORES) {
                    scoreDebug.beginning += BEGINNING_OF_NAME_POINTS;
                }
                newPoints += BEGINNING_OF_NAME_POINTS;
            }

            // If the new character immediately follows the last matched character,
            // we award the consecutive matches bonus. The check for score > 0
            // handles the initial value of lastMatchIndex which is used for
            // constructing ranges but we don't yet have a true match.
            if (score > 0 && lastMatchIndex + 1 === c) {
                // Continue boosting for each additional match at the beginning
                // of the name
                if (c - numConsecutive === lastSegmentStart) {
                    if (DEBUG_SCORES) {
                        scoreDebug.beginning += BEGINNING_OF_NAME_POINTS;
                    }
                    newPoints += BEGINNING_OF_NAME_POINTS;
                }

                numConsecutive++;

                var boost = CONSECUTIVE_MATCHES_POINTS * numConsecutive;

                // Consecutive matches that started on a special are a
                // good indicator of intent, so we award an added bonus there.
                if (currentRangeStartedOnSpecial) {
                    boost = boost * 2;
                }

                if (DEBUG_SCORES) {
                    scoreDebug.consecutive += boost;
                }
                newPoints += boost;
            } else {
                numConsecutive = 1;
            }

            // add points for "special" character matches
            if (match instanceof SpecialMatch) {
                if (DEBUG_SCORES) {
                    scoreDebug.special += SPECIAL_POINTS;
                }
                newPoints += SPECIAL_POINTS;
            }

            score += newPoints;

            // points accumulated in the last segment get an extra bonus
            if (c >= lastSegmentStart) {
                lastSegmentScore += newPoints;
            }

            // if the last range wasn't a match or there's a gap, we need to close off
            // the range to start a new one.
            if ((currentRange && !currentRange.matched) || c > lastMatchIndex + 1) {
                closeRangeGap(c);
            }
            lastMatchIndex = c;

            // set up a new match range or add to the current one
            if (!currentRange) {
                currentRange = {
                    text: str[c],
                    matched: true
                };

                // Check to see if this new matched range is starting on a special
                // character. We penalize those ranges that don't, because most
                // people will search on the logical boundaries of the name
                currentRangeStartedOnSpecial = match instanceof SpecialMatch;
            } else {
                currentRange.text += str[c];
            }
        }

        // scan through the matches, adding each one in turn
        for (matchCounter = 0; matchCounter < matchList.length; matchCounter++) {
            var match = matchList[matchCounter];
            addMatch(match);
        }

        // add a range for the last part of the string
        closeRangeGap(str.length);

        // shorter strings that match are often better than longer ones
        var lengthPenalty = -1 * Math.round(str.length * DEDUCTION_FOR_LENGTH);
        if (DEBUG_SCORES) {
            scoreDebug.lengthDeduction = lengthPenalty;
        }
        score = score + lengthPenalty;

        var result = {
            ranges: ranges,
            matchGoodness: score
        };

        if (DEBUG_SCORES) {
            result.scoreDebug = scoreDebug;
        }
        return result;
    }

    /*
     * If we short circuit normal matching to produce a prefix match,
     * this function will generate the appropriate SearchResult.
     * This function assumes that the prefix match check has already
     * been performed.
     *
     * @param {string} str  The string with the prefix match for the query
     * @param {string} query  The query that matched the beginning of str
     * @return {{ranges:Array.<{text:string, matched:boolean, includesLastSegment:boolean}>, matchGoodness:int, scoreDebug: Object}} ranges has a matching range for beginning of str
     *                      and a non-matching range for the end of the str
     *                      the score is -Number.MAX_VALUE in all cases
     */
    function _prefixMatchResult(str, query) {
        var result = new SearchResult(str);

        result.matchGoodness = -Number.MAX_VALUE;

        if (str.substr(0, query.length) !== query) {
            // Penalize for not matching case
            result.matchGoodness *= 0.5;
        }

        if (DEBUG_SCORES) {
            result.scoreDebug = {
                beginning: -result.matchGoodness
            };
        }

        result.stringRanges = [{
            text: str.substr(0, query.length),
            matched: true,
            includesLastSegment: true
        }];
        if (str.length > query.length) {
            result.stringRanges.push({
                text: str.substring(query.length),
                matched: false,
                includesLastSegment: true
            });
        }
        return result;
    }

    /*
     * Match str against the query using the QuickOpen algorithm provided by
     * the functions above. The general idea is to prefer matches of "special" characters and,
     * optionally, matches that occur in the "last segment" (generally, the filename). stringMatch
     * will try to provide the best match and produces a "matchGoodness" score
     * to allow for relative ranking.
     *
     * The result object returned includes "stringRanges" which can be used to highlight
     * the matched portions of the string, in addition to the "matchGoodness"
     * mentioned above. If DEBUG_SCORES is true, scoreDebug is set on the result
     * to provide insight into the score.
     *
     * The matching is done in a case-insensitive manner.
     *
     * @param {string} str  The string to search
     * @param {string} query  The query string to find in string
     * @param {{preferPrefixMatches:?boolean, segmentedSearch:?boolean}} options to control search behavior.
     *                  preferPrefixMatches puts an exact case-insensitive prefix match ahead of all other matches,
     *                  even short-circuiting the match logic. This option implies segmentedSearch=false.
     *                  When segmentedSearch is true, the string is broken into segments by "/" characters
     *                  and the last segment is searched first and matches there are scored higher.
     * @param {?Object} special (optional) the specials data from findSpecialCharacters, if already known
     *                  This is generally just used by StringMatcher for optimization.
     * @return {{ranges:Array.<{text:string, matched:boolean, includesLastSegment:boolean}>, matchGoodness:int, scoreDebug: Object}} matched ranges and score
     */
    function stringMatch(str, query, options, special) {
        var result;

        options = options || {};

        // No query? Short circuit the normal work done and just
        // return a single range that covers the whole string.
        if (!query) {
            result = new SearchResult(str);
            result.matchGoodness = 0;
            if (DEBUG_SCORES) {
                result.scoreDebug = {};
            }
            result.stringRanges = [{
                text: str,
                matched: false,
                includesLastSegment: true
            }];
            return result;
        }

        // comparisons are case insensitive, so switch to lower case here
        var queryLower = query.toLowerCase();
        var compareLower = str.toLowerCase();

        if (options.preferPrefixMatches) {
            options.segmentedSearch = false;
        }

        if (options.preferPrefixMatches && compareLower.substr(0, queryLower.length) === queryLower) {
            // NOTE: we compare against the case insensitive match
            //        above but we pass the case-sensitive version in
            //        because we want to weight the match to give case-matches
            //        a higher score
            return _prefixMatchResult(str, query);
        }

        // Locate the special characters and then use orderedCompare to do the real
        // work.
        if (!special) {
            special = findSpecialCharacters(str);
        }
        var lastSegmentStart, matchList;

        // For strings that are not broken into multiple segments, we can potentially
        // avoid some extra work
        if (options.segmentedSearch) {
            lastSegmentStart = special.specials[special.lastSegmentSpecialsIndex];
            matchList = _wholeStringSearch(queryLower, compareLower, query, str, special.specials,
                              special.lastSegmentSpecialsIndex);
        } else {
            lastSegmentStart = 0;
            matchList = _generateMatchList(queryLower, compareLower, query, str, special.specials, 0);
        }

        // If we get a match, turn this into a SearchResult as expected by the consumers
        // of this API.
        if (matchList) {
            var compareData = _computeRangesAndScore(matchList, str, lastSegmentStart);
            result = new SearchResult(str);
            result.stringRanges = compareData.ranges;
            result.matchGoodness = -1 * compareData.matchGoodness;
            if (DEBUG_SCORES) {
                result.scoreDebug = compareData.scoreDebug;
            }
        }
        return result;
    }

    /**
     * Sorts an array of SearchResult objects on a primary field, followed by secondary fields
     * in case of ties. 'fieldSpec' provides the priority order for fields, where the first entry is the primary field, for example:
     *      multiFieldSort(bugList, [ "milestone", "severity" ]);
     * Would sort a bug list by milestone, and within each milestone sort bugs by severity.
     *
     * fieldSpec can also include comparator functions of the form normally used by the sort()
     * function.
     *
     * Any fields that have a string value are compared case-insensitively. Fields used should be
     * present on all SearchResult objects (no optional/undefined fields).
     *
     * @param {!Array.<SearchResult>} searchResults
     * @param {!Array.<string, function>} fieldSpec
     */
    function multiFieldSort(searchResults, fieldSpec) {
        // Move field names into an array, with primary field first
        var comparisons;
        if (Array.isArray(fieldSpec)) {
            comparisons = fieldSpec;
        } else {
            // TODO Deprecate this form of calling this function
            comparisons = [];
            _.forEach(fieldSpec, function (priority, key) {
                comparisons[priority] = key;
            });
        }

        searchResults.sort(function (a, b) {
            var priority;
            for (priority = 0; priority < comparisons.length; priority++) {
                var comparison = comparisons[priority];
                if (typeof comparison === "function") {
                    var result = comparison(a, b);
                    if (result) {
                        return result;
                    }
                } else {
                    var valueA = a[comparison];
                    var valueB = b[comparison];
                    if (typeof valueA === "string") {
                        valueA = valueA.toLowerCase();
                        valueB = valueB.toLowerCase();
                    }

                    if (valueA < valueB) {
                        return -1;
                    } else if (valueA > valueB) {
                        return 1;
                    }
                }
                // otherwise, move on to next sort priority
            }
            return 0; // all sort fields are equal
        });
    }

    /**
     * Sorts search results generated by stringMatch(): results are sorted into several
     * tiers based on how well they matched the search query, then sorted alphabetically
     * within each tier.
     */
    function basicMatchSort(searchResults) {
        multiFieldSort(searchResults, { matchGoodness: 0, label: 1 });
    }

    /**
     * A StringMatcher provides an interface to the stringMatch function with built-in
     * caching. You should use a StringMatcher for the lifetime of queries over a
     * single data set.
     *
     * You are free to store other data on this object to assist in higher-level caching.
     * (This object's caches are all stored in "_" prefixed properties.)
     *
     * @param {{preferPrefixMatches:?boolean, segmentedSearch:?boolean}} options to control search behavior.
     *                  preferPrefixMatches puts an exact case-insensitive prefix match ahead of all other matches,
     *                  even short-circuiting the match logic. This option implies segmentedSearch=false.
     *                  segmentedSearch treats segments of the string specially.
     */
    function StringMatcher(options) {
        this.options = options;
        this.reset();
    }

    /**
     * Map from search-result string to the findSpecialCharacters() result for that string - easy to cache
     * since this info doesn't change as the query changes.
     * @type {Object.<string, {specials:Array.<number>, lastSegmentSpecialsIndex:number}>}
     */
    StringMatcher.prototype._specialsCache = null;

    /**
     * Set of search-result strings that we know don't match the query _lastQuery - or any other query with
     * that prefix.
     * @type {Object.<string, boolean>}
     */
    StringMatcher.prototype._noMatchCache = null;

    /**
     * Clears the caches. Use this in the event that the caches may be invalid.
     */
    StringMatcher.prototype.reset = function () {
        // We keep track of the last query to know when we need to invalidate.
        this._lastQuery = null;

        this._specialsCache = {};
        this._noMatchCache = {};
    };

    /**
     * Performs a single match using the stringMatch function. See stringMatch for full documentation.
     *
     * @param {string} str  The string to search
     * @param {string} query  The query string to find in string
     * @return {{ranges:Array.<{text:string, matched:boolean, includesLastSegment:boolean}>, matchGoodness:int, scoreDebug: Object}} matched ranges and score
     */
    StringMatcher.prototype.match = function (str, query) {

        // If the query is not just added characters from the previous query, we invalidate
        // the no match cache and will re-match everything.
        if (this._lastQuery !== null && (this._lastQuery !== query.substring(0, this._lastQuery.length))) {
            this._noMatchCache = {};
        }

        this._lastQuery = query;

        // Check for a known non-matching string.
        if (_.has(this._noMatchCache, str)) {
            return undefined;
        }

        // Load up the cached specials information (or build it if this is our first time through).
        var special = _.has(this._specialsCache, str) ? this._specialsCache[str] : undefined;
        if (special === undefined) {
            special = findSpecialCharacters(str);
            this._specialsCache[str] = special;
        }

        var result = stringMatch(str, query, this.options, special);

        // If this query was not a match, we cache that fact for next time.
        if (!result) {
            this._noMatchCache[str] = true;
        }
        return result;
    };

    exports._findSpecialCharacters  = findSpecialCharacters;
    exports._wholeStringSearch      = _wholeStringSearch;
    exports._lastSegmentSearch      = _lastSegmentSearch;
    exports._setDebugScores         = _setDebugScores;
    exports._generateMatchList      = _generateMatchList;
    exports._SpecialMatch           = SpecialMatch;
    exports._NormalMatch            = NormalMatch;
    exports._computeRangesAndScore  = _computeRangesAndScore;

    // public exports
    exports.SearchResult            = SearchResult;
    exports.stringMatch             = stringMatch;
    exports.basicMatchSort          = basicMatchSort;
    exports.multiFieldSort          = multiFieldSort;
    exports.StringMatcher           = StringMatcher;
});


[![Build Status](https://travis-ci.org/superjoe30/node-multiparty.png?branch=master)](https://travis-ci.org/superjoe30/node-multiparty)
# multiparty

Parse http requests with content-type `multipart/form-data`, also known as file uploads.

### Why the fork?

 * This module uses the Node.js v0.10 streams properly, *even in Node.js v0.8*
 * It will not create a temp file for you unless you want it to.
 * Counts bytes and does math to help you figure out the `Content-Length` of
   each part.
 * You can easily stream uploads to s3 with
   [knox](https://github.com/LearnBoost/knox), for [example](examples/s3.js).
 * Less bugs. This code is simpler, has all deprecated functionality removed,
   has cleaner tests, and does not try to do anything beyond multipart stream
   parsing.

## Installation

```
npm install multiparty
```

## Usage

 * See [examples](examples).
 * Using express or connect? See [connect-multiparty](https://github.com/superjoe30/connect-multiparty)

Parse an incoming `multipart/form-data` request.

```js
var multiparty = require('multiparty')
  , http = require('http')
  , util = require('util')

http.createServer(function(req, res) {
  if (req.url === '/upload' && req.method === 'POST') {
    // parse a file upload
    var form = new multiparty.Form();

    form.parse(req, function(err, fields, files) {
      res.writeHead(200, {'content-type': 'text/plain'});
      res.write('received upload:\n\n');
      res.end(util.inspect({fields: fields, files: files}));
    });

    return;
  }

  // show a file upload form
  res.writeHead(200, {'content-type': 'text/html'});
  res.end(
    '<form action="/upload" enctype="multipart/form-data" method="post">'+
    '<input type="text" name="title"><br>'+
    '<input type="file" name="upload" multiple="multiple"><br>'+
    '<input type="submit" value="Upload">'+
    '</form>'
  );
}).listen(8080);
```

## API

### multiparty.Form
```js
var form = new multiparty.Form(options)
```
Creates a new form. Options:

 * `encoding` - sets encoding for the incoming form fields. Defaults to `utf8`.
 * `maxFieldSize` - Limits the amount of memory a field (not a file) can
   allocate in bytes. If this value is exceeded, an `error` event is emitted.
   The default size is 2MB.
 * `maxFields` - Limits the number of fields that will be parsed before
   emitting an `error` event. A file counts as a field in this case.
   Defaults to 1000.
 * `autoFields` - Enables `field` events. This is automatically set to `true`
   if you add a `field` listener.
 * `autoFiles` - Enables `file` events. This is automatically set to `true`
   if you add a `file` listener.
 * `uploadDir` - Only relevant when `autoFiles` is `true`. The directory for
   placing file uploads in. You can move them later using `fs.rename()`.
   Defaults to `os.tmpDir()`.
 * `hash` - Only relevant when `autoFiles` is `true`. If you want checksums
   calculated for incoming files, set this to either `sha1` or `md5`.
   Defaults to off.

#### form.parse(request, [cb])

Parses an incoming node.js `request` containing form data. If `cb` is
provided, `autoFields` and `autoFiles` are set to `true` and all fields and
files are collected and passed to the callback:

```js
form.parse(req, function(err, fields, files) {
  // ...
});
```

#### form.bytesReceived

The amount of bytes received for this form so far.

#### form.bytesExpected

The expected number of bytes in this form.

### Events

#### 'error' (err)

You definitely want to handle this event. If not your server *will* crash when
users submit bogus multipart requests!

#### 'part' (part)

Emitted when a part is encountered in the request. `part` is a
`ReadableStream`. It also has the following properties:

 * `headers` - the headers for this part. For example, you may be interested
   in `content-type`.
 * `name` - the field name for this part
 * `filename` - only if the part is an incoming file
 * `byteOffset` - the byte offset of this part in the request body
 * `byteCount` - assuming that this is the last part in the request,
   this is the size of this part in bytes. You could use this, for
   example, to set the `Content-Length` header if uploading to S3.
   If the part had a `Content-Length` header then that value is used
   here instead.

#### 'aborted'

Emitted when the request is aborted. This event will be followed shortly
by an `error` event. In practice you do not need to handle this event.

#### 'progress' (bytesReceived, bytesExpected)

#### 'close'

Emitted after all parts have been parsed and emitted. Not emitted if an `error`
event is emitted. This is typically when you would send your response.

#### 'file' (name, file)

**By default multiparty will not touch your hard drive.** But if you add this
listener, multiparty automatically sets `form.autoFiles` to `true` and will
stream uploads to disk for you.

 * `name` - the field name for this file
 * `file` - an object with these properties:
   - `originalFilename` - the filename that the user reports for the file
   - `path` - the absolute path of the uploaded file on disk
   - `headers` - the HTTP headers that were sent along with this file
   - `size` - size of the file in bytes

If you set the `form.hash` option, then `file` will also contain a `hash`
property which is the checksum of the file.

#### 'field' (name, value)

 * `name` - field name
 * `value` - string field value


keep keep keep replace replace keep keep keep keep


/*!
 * Connect - csrf
 * Copyright(c) 2011 Sencha Inc.
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var utils = require('../utils');
var uid = require('uid2');
var crypto = require('crypto');

/**
 * Anti CSRF:
 *
 * CSRF protection middleware.
 *
 * This middleware adds a `req.csrfToken()` function to make a token
 * which should be added to requests which mutate
 * state, within a hidden form field, query-string etc. This
 * token is validated against the visitor's session.
 *
 * The default `value` function checks `req.body` generated
 * by the `bodyParser()` middleware, `req.query` generated
 * by `query()`, and the "X-CSRF-Token" header field.
 *
 * This middleware requires session support, thus should be added
 * somewhere _below_ `session()` and `cookieParser()`.
 *
 * Options:
 *
 *    - `value` a function accepting the request, returning the token
 *
 * @param {Object} options
 * @api public
 */

module.exports = function csrf(options) {
  options = options || {};
  var value = options.value || defaultValue;

  return function(req, res, next){

    // already have one
    var secret = req.session._csrfSecret;
    if (secret) return createToken(secret);

    // generate secret
    uid(24, function(err, secret){
      if (err) return next(err);
      req.session._csrfSecret = secret;
      createToken(secret);
    });

    // generate the token
    function createToken(secret) {
      var token;

      // lazy-load token
      req.csrfToken = function csrfToken() {
        return token || (token = saltedToken(secret));
      };

      // compatibility with old middleware
      Object.defineProperty(req.session, '_csrf', {
        configurable: true,
        get: function() {
          console.warn('req.session._csrf is deprecated, use req.csrfToken() instead');
          return req.csrfToken();
        }
      });

      // ignore these methods
      if ('GET' == req.method || 'HEAD' == req.method || 'OPTIONS' == req.method) return next();

      // determine user-submitted value
      var val = value(req);

      // check
      if (!checkToken(val, secret)) return next(utils.error(403));

      next();
    }
  }
};

/**
 * Default value function, checking the `req.body`
 * and `req.query` for the CSRF token.
 *
 * @param {IncomingMessage} req
 * @return {String}
 * @api private
 */

function defaultValue(req) {
  return (req.body && req.body._csrf)
    || (req.query && req.query._csrf)
    || (req.headers['x-csrf-token'])
    || (req.headers['x-xsrf-token']);
}

/**
 * Return salted token.
 *
 * @param {String} secret
 * @return {String}
 * @api private
 */

function saltedToken(secret) {
  return createToken(generateSalt(10), secret);
}

/**
 * Creates a CSRF token from a given salt and secret.
 *
 * @param {String} salt (should be 10 characters)
 * @param {String} secret
 * @return {String}
 * @api private
 */

function createToken(salt, secret) {
  return salt + crypto
    .createHash('sha1')
    .update(salt + secret)
    .digest('base64');
}

/**
 * Checks if a given CSRF token matches the given secret.
 *
 * @param {String} token
 * @param {String} secret
 * @return {Boolean}
 * @api private
 */

function checkToken(token, secret) {
  if ('string' != typeof token) return false;
  return token === createToken(token.slice(0, 10), secret);
}

/**
 * Generates a random salt, using a fast non-blocking PRNG (Math.random()).
 *
 * @param {Number} length
 * @return {String}
 * @api private
 */

function generateSalt(length) {
  var i, r = [];
  for (i = 0; i < length; ++i) {
    r.push(SALTCHARS[Math.floor(Math.random() * SALTCHARS.length)]);
  }
  return r.join('');
}

var SALTCHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';


/* Test comment */
define(function (require, exports, module) {
    var bar = require("modules/bar"),
        Bar = require("modules/Bar"),
        Baz = require("modules/Baz");

    function callbar() {

        bar();

    }

}


var spawn = require('child_process').spawn
  , exitCode = 0
  , timeout = 10000
  , fs = require('fs')
  ;

fs.readdir(__dirname, function (e, files) {
  if (e) throw e

  var tests = files.filter(function (f) {return f.slice(0, 'test-'.length) === 'test-'})

  var next = function () {
    if (tests.length === 0) process.exit(exitCode);

    var file = tests.shift()
    console.log(file)
    var proc = spawn('node', [ 'tests/' + file ])

    var killed = false
    var t = setTimeout(function () {
      proc.kill()
      exitCode += 1
      console.error(file + ' timeout')
      killed = true
    }, timeout)

    proc.stdout.pipe(process.stdout)
    proc.stderr.pipe(process.stderr)
    proc.on('exit', function (code) {
      if (code && !killed) console.error(file + ' failed')
      exitCode += code || 0
      clearTimeout(t)
      next()
    })
  }
  next()

})