/*
 * Copyright 2014 The Chromium Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

/**
 * @constructor
 * @extends {WebInspector.Object}
 * @param {!Element} element
 * @param {boolean=} disableRotate
 */
WebInspector.TransformController = function(element, disableRotate)
{
    this._shortcuts = {};
    this.element = element;
    if (this.element.tabIndex < 0)
        this.element.tabIndex = 0;
    this._registerShortcuts();
    WebInspector.installDragHandle(element, this._onDragStart.bind(this), this._onDrag.bind(this), this._onDragEnd.bind(this), "move", null);
    element.addEventListener("keydown", this._onKeyDown.bind(this), false);
    element.addEventListener("keyup", this._onKeyUp.bind(this), false);
    element.addEventListener("mousewheel", this._onMouseWheel.bind(this), false);
    this._minScale = 0;
    this._maxScale = Infinity;

    this._controlPanelToolbar = new WebInspector.Toolbar("transform-control-panel");

    /** @type {!Object<string, !WebInspector.ToolbarToggle>} */
    this._modeButtons = {};
    if (!disableRotate) {
        var panModeButton = new WebInspector.ToolbarToggle(WebInspector.UIString("Pan mode (X)"), "pan-toolbar-item");
        panModeButton.addEventListener("click", this._setMode.bind(this, WebInspector.TransformController.Modes.Pan));
        this._modeButtons[WebInspector.TransformController.Modes.Pan] = panModeButton;
        this._controlPanelToolbar.appendToolbarItem(panModeButton);
        var rotateModeButton = new WebInspector.ToolbarToggle(WebInspector.UIString("Rotate mode (V)"), "rotate-toolbar-item");
        rotateModeButton.addEventListener("click", this._setMode.bind(this, WebInspector.TransformController.Modes.Rotate));
        this._modeButtons[WebInspector.TransformController.Modes.Rotate] = rotateModeButton;
        this._controlPanelToolbar.appendToolbarItem(rotateModeButton);
    }
    this._setMode(WebInspector.TransformController.Modes.Pan);

    var resetButton = new WebInspector.ToolbarButton(WebInspector.UIString("Reset transform (0)"), "center-toolbar-item");
    resetButton.addEventListener("click", this.resetAndNotify.bind(this, undefined));
    this._controlPanelToolbar.appendToolbarItem(resetButton);

    this._reset();
}

/**
 * @enum {string}
 */
WebInspector.TransformController.Events = {
    TransformChanged: "TransformChanged"
}

/**
 * @enum {string}
 */
WebInspector.TransformController.Modes = {
    Pan: "Pan",
    Rotate: "Rotate",
}

WebInspector.TransformController.prototype = {
    /**
     * @return {!WebInspector.Toolbar}
     */
    toolbar: function()
    {
        return this._controlPanelToolbar;
    },

    _onKeyDown: function(event)
    {
        if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Shift.code) {
            this._toggleMode();
            return;
        }

        var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEventIgnoringModifiers(event);
        var handler = this._shortcuts[shortcutKey];
        if (handler && handler(event))
            event.consume();
    },

    _onKeyUp: function(event)
    {
        if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Shift.code)
            this._toggleMode();
    },

    _addShortcuts: function(keys, handler)
    {
        for (var i = 0; i < keys.length; ++i)
            this._shortcuts[keys[i].key] = handler;
    },

    _registerShortcuts: function()
    {
        this._addShortcuts(WebInspector.ShortcutsScreen.LayersPanelShortcuts.ResetView, this.resetAndNotify.bind(this));
        this._addShortcuts(WebInspector.ShortcutsScreen.LayersPanelShortcuts.PanMode, this._setMode.bind(this, WebInspector.TransformController.Modes.Pan));
        this._addShortcuts(WebInspector.ShortcutsScreen.LayersPanelShortcuts.RotateMode, this._setMode.bind(this, WebInspector.TransformController.Modes.Rotate));
        var zoomFactor = 1.1;
        this._addShortcuts(WebInspector.ShortcutsScreen.LayersPanelShortcuts.ZoomIn, this._onKeyboardZoom.bind(this, zoomFactor));
        this._addShortcuts(WebInspector.ShortcutsScreen.LayersPanelShortcuts.ZoomOut, this._onKeyboardZoom.bind(this, 1 / zoomFactor));
        this._addShortcuts(WebInspector.ShortcutsScreen.LayersPanelShortcuts.Up, this._onKeyboardPanOrRotate.bind(this, 0, -1));
        this._addShortcuts(WebInspector.ShortcutsScreen.LayersPanelShortcuts.Down, this._onKeyboardPanOrRotate.bind(this, 0, 1));
        this._addShortcuts(WebInspector.ShortcutsScreen.LayersPanelShortcuts.Left, this._onKeyboardPanOrRotate.bind(this, -1, 0));
        this._addShortcuts(WebInspector.ShortcutsScreen.LayersPanelShortcuts.Right, this._onKeyboardPanOrRotate.bind(this, 1, 0));
    },

    _postChangeEvent: function()
    {
        this.dispatchEventToListeners(WebInspector.TransformController.Events.TransformChanged);
    },

    _reset: function()
    {
        this._scale = 1;
        this._offsetX = 0;
        this._offsetY = 0;
        this._rotateX = 0;
        this._rotateY = 0;
    },

    _toggleMode: function()
    {
        this._setMode(this._mode === WebInspector.TransformController.Modes.Pan ? WebInspector.TransformController.Modes.Rotate : WebInspector.TransformController.Modes.Pan);
    },

    /**
     * @param {!WebInspector.TransformController.Modes} mode
     */
    _setMode: function(mode)
    {
        if (this._mode === mode)
            return;
        this._mode = mode;
        this._updateModeButtons();
        this.element.focus();
    },

    _updateModeButtons: function()
    {
        for (var mode in this._modeButtons)
            this._modeButtons[mode].setToggled(mode === this._mode);
    },

    /**
     * @param {!Event=} event
     */
    resetAndNotify: function(event)
    {
        this._reset();
        this._postChangeEvent();
        if (event)
            event.preventDefault();
        this.element.focus();
    },

    /**
     * @param {number} minScale
     * @param {number} maxScale
     */
    setScaleConstraints: function(minScale, maxScale)
    {
        this._minScale = minScale;
        this._maxScale = maxScale;
        this._scale = Number.constrain(this._scale, minScale, maxScale);
    },

    /**
     * @param {number} minX
     * @param {number} maxX
     * @param {number} minY
     * @param {number} maxY
     */
    clampOffsets: function(minX, maxX, minY, maxY)
    {
        this._offsetX = Number.constrain(this._offsetX, minX, maxX);
        this._offsetY = Number.constrain(this._offsetY, minY, maxY);
    },

    /**
     * @return {number}
     */
    scale: function()
    {
        return this._scale;
    },

    /**
     * @return {number}
     */
    offsetX: function()
    {
        return this._offsetX;
    },

    /**
     * @return {number}
     */
    offsetY: function()
    {
        return this._offsetY;
    },

    /**
     * @return {number}
     */
    rotateX: function()
    {
        return this._rotateX;
    },

    /**
     * @return {number}
     */
    rotateY: function()
    {
        return this._rotateY;
    },

    /**
     * @param {number} scaleFactor
     * @param {number} x
     * @param {number} y
     */
    _onScale: function(scaleFactor, x, y)
    {
        scaleFactor = Number.constrain(this._scale * scaleFactor, this._minScale, this._maxScale) / this._scale;
        this._scale *= scaleFactor;
        this._offsetX -= (x - this._offsetX) * (scaleFactor - 1);
        this._offsetY -= (y - this._offsetY) * (scaleFactor - 1);
        this._postChangeEvent();
    },

    /**
     * @param {number} offsetX
     * @param {number} offsetY
     */
    _onPan: function(offsetX, offsetY)
    {
        this._offsetX += offsetX;
        this._offsetY += offsetY;
        this._postChangeEvent();
    },

    /**
     * @param {number} rotateX
     * @param {number} rotateY
     */
    _onRotate: function(rotateX, rotateY)
    {
        this._rotateX = rotateX;
        this._rotateY = rotateY;
        this._postChangeEvent();
    },

    /**
     * @param {number} zoomFactor
     */
    _onKeyboardZoom: function(zoomFactor)
    {
        this._onScale(zoomFactor, this.element.clientWidth / 2, this.element.clientHeight / 2);
    },

    /**
     * @param {number} xMultiplier
     * @param {number} yMultiplier
     */
    _onKeyboardPanOrRotate: function(xMultiplier, yMultiplier)
    {
        var panStepInPixels = 6;
        var rotateStepInDegrees = 5;

        if (this._mode === WebInspector.TransformController.Modes.Rotate) {
            // Sic! _onRotate treats X and Y as "rotate around X" and "rotate around Y", so swap X/Y multiplers.
            this._onRotate(this._rotateX + yMultiplier * rotateStepInDegrees, this._rotateY + xMultiplier * rotateStepInDegrees);
        } else {
            this._onPan(xMultiplier * panStepInPixels, yMultiplier * panStepInPixels);
        }
    },

    /**
     * @param {!Event} event
     */
    _onMouseWheel: function(event)
    {
        /** @const */
        var zoomFactor = 1.1;
        /** @const */
        var mouseWheelZoomSpeed = 1 / 120;
        var scaleFactor = Math.pow(zoomFactor, event.wheelDeltaY * mouseWheelZoomSpeed);
        this._onScale(scaleFactor, event.clientX - this.element.totalOffsetLeft(), event.clientY - this.element.totalOffsetTop());
    },

    /**
     * @param {!Event} event
     */
    _onDrag: function(event)
    {
        if (this._mode === WebInspector.TransformController.Modes.Rotate) {
            this._onRotate(this._oldRotateX + (this._originY - event.clientY) / this.element.clientHeight * 180, this._oldRotateY - (this._originX - event.clientX) / this.element.clientWidth * 180);
        } else {
            this._onPan(event.clientX - this._originX, event.clientY - this._originY);
            this._originX = event.clientX;
            this._originY = event.clientY;
        }
    },

    /**
     * @param {!MouseEvent} event
     */
    _onDragStart: function(event)
    {
        this.element.focus();
        this._originX = event.clientX;
        this._originY = event.clientY;
        this._oldRotateX = this._rotateX;
        this._oldRotateY = this._rotateY;
        return true;
    },

    _onDragEnd: function()
    {
        delete this._originX;
        delete this._originY;
        delete this._oldRotateX;
        delete this._oldRotateY;
    },

    __proto__: WebInspector.Object.prototype
}
