/// <reference path="../node_modules/pxt-core/localtypings/blockly.d.ts"/>
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>

export interface FieldMotorsOptions extends Blockly.FieldCustomDropdownOptions {

}

export class FieldMotors extends Blockly.FieldDropdown implements Blockly.FieldCustom {
    public isFieldCustom_ = true;

    private box2_: SVGRectElement;
    private textElement2_: SVGTextElement;
    private arrow2_: SVGImageElement;

    // Width in pixels
    protected itemWidth_: number;

    // Number of rows to display (if there are extra rows, the picker will be scrollable)
    protected maxRows_: number;

    protected backgroundColour_: string;
    protected itemColour_: string;
    protected borderColour_: string;

    private isFirst_: boolean; // which of the two dropdowns is selected

    constructor(text: string, options: FieldMotorsOptions, validator?: Function) {
        super(options.data, validator);

        this.itemWidth_ = 75;
        this.backgroundColour_ = pxtblockly.parseColour(options.colour);
        this.itemColour_ = "rgba(255, 255, 255, 0.6)";
        this.borderColour_ = pxt.toolbox.fadeColor(this.backgroundColour_, 0.4, false);
    }

    init() {
        if (this.fieldGroup_) {
            // Field has already been initialized once.
            return;
        }
        // Add dropdown arrow: "option ▾" (LTR) or "▾ אופציה" (RTL)
        // Positioned on render, after text size is calculated.
        /** @type {Number} */
        (this as any).arrowSize_ = 12;
        /** @type {Number} */
        (this as any).arrowX_ = 0;
        /** @type {Number} */
        this.arrowY_ = 11;
        this.arrow_ = Blockly.utils.dom.createSvgElement('image', {
            'height': (this as any).arrowSize_ + 'px',
            'width': (this as any).arrowSize_ + 'px'
        }, null);
        this.arrow_.setAttributeNS('http://www.w3.org/1999/xlink',
            'xlink:href', (Blockly.FieldDropdown as any).DROPDOWN_SVG_DATAURI);

        this.arrow2_ = <SVGImageElement>Blockly.utils.dom.createSvgElement('image', {
            'height': (this as any).arrowSize_ + 'px',
            'width': (this as any).arrowSize_ + 'px'
        }, null);
        this.arrow2_.setAttributeNS('http://www.w3.org/1999/xlink',
            'xlink:href', (Blockly.FieldDropdown as any).DROPDOWN_SVG_DATAURI);
        (this as any).className_ += ' blocklyDropdownText';

        // Build the DOM.
        this.fieldGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null);
        if (!this.visible_) {
            (this.fieldGroup_ as any).style.display = 'none';
        }
        // Adjust X to be flipped for RTL. Position is relative to horizontal start of source block.
        let size = this.getSize();
        let fieldX = (this.sourceBlock_.RTL) ? -size.width / 2 : size.width / 2;
        /** @type {!Element} */
        this.textElement_ = Blockly.utils.dom.createSvgElement('text',
            {
                'class': (this as any).className_,
                'x': fieldX,
                'dy': '0.7ex',
                'y': size.height / 2
            },
            this.fieldGroup_);
        fieldX += 10; // size of first group.
        this.textElement2_ = <SVGTextElement>Blockly.utils.dom.createSvgElement('text',
            {
                'class': (this as any).className_,
                'x': fieldX,
                'dy': '0.7ex',
                'y': this.size_.height / 2
            },
            this.fieldGroup_);

        this.updateEditable();
        (this.sourceBlock_ as Blockly.BlockSvg).getSvgRoot().appendChild(this.fieldGroup_);
        // Force a render.
        this.render_();
        this.isDirty_ = true;
        (this as any).mouseDownWrapper_ =
            Blockly.bindEventWithChecks_((this as any).getClickTarget_(), 'mousedown', this,
                (this as any).onMouseDown_);

        // Add second dropdown 
        if (this.shouldShowRect_()) {
            this.box_ = <SVGRectElement>Blockly.utils.dom.createSvgElement('rect', {
                'rx': (Blockly.BlockSvg as any).CORNER_RADIUS,
                'ry': (Blockly.BlockSvg as any).CORNER_RADIUS,
                'x': 0,
                'y': 0,
                'width': this.size_.width,
                'height': this.size_.height,
                'stroke': this.sourceBlock_.getColourTertiary(),
                'fill': this.sourceBlock_.getColour(),
                'class': 'blocklyBlockBackground',
                'fill-opacity': 1
            }, null);
            this.fieldGroup_.insertBefore(this.box_, this.textElement_);
            this.box2_ = <SVGRectElement>Blockly.utils.dom.createSvgElement('rect', {
                'rx': (Blockly.BlockSvg as any).CORNER_RADIUS,
                'ry': (Blockly.BlockSvg as any).CORNER_RADIUS,
                'x': 0,
                'y': 0,
                'width': this.size_.width,
                'height': this.size_.height,
                'stroke': this.sourceBlock_.getColourTertiary(),
                'fill': this.sourceBlock_.getColour(),
                'class': 'blocklyBlockBackground',
                'fill-opacity': 1
            }, null);
            this.fieldGroup_.insertBefore(this.box2_, this.textElement2_);
        }

        // Force a reset of the text to add the arrow.
        let text = this.text_;
        this.text_ = null;
        this.setText(text);
    }

    getFirstValue(value: string) {
        const typeValue = value.indexOf('large') != -1 ? 'large' : 'medium';
        const portValue = this.getSecondValue(value);
        const isDual = portValue.length > 1;
        return `${typeValue} motor${isDual ? 's' : ''}`;
    }

    getSecondValue(value: string) {
        return (value.indexOf('large') != -1) ?
            value.substring(value.indexOf('large') + 5) :
            value.substring(value.indexOf('medium') + 6);
    }

    getFirstValueI11n(value: string) {
        const firstValue = this.getFirstValue(value);
        const motorOptions = {
            'medium motor': lf("medium motor"),
            'large motor': lf("large motor"),
            'large motors': lf("large motors")
        }
        return motorOptions[firstValue];
    }

    private normalizeText_(text: string) {
        if (!text) {
            // Prevent the field from disappearing if empty.
            return Blockly.Field.NBSP;
        }
        if (text.length > this.maxDisplayLength) {
            // Truncate displayed string and add an ellipsis ('...').
            text = text.substring(0, this.maxDisplayLength - 2) + '\u2026';
        }
        // Replace whitespace with non-breaking spaces so the text doesn't collapse.
        text = text.replace(/\s/g, Blockly.Field.NBSP);
        if (this.sourceBlock_.RTL) {
            // The SVG is LTR, force text to be RTL.
            text += '\u200F';
        }
        return text;
    }

    updateTextNode2_() {
        if (!this.textElement2_) {
            // Not rendered yet.
            return;
        }
        let text = this.text_;
        if (text.length > this.maxDisplayLength) {
            // Truncate displayed string and add an ellipsis ('...').
            text = text.substring(0, this.maxDisplayLength - 2) + '\u2026';
            // Add special class for sizing font when truncated
            this.textElement2_.setAttribute('class', (this as any).className_ + ' blocklyTextTruncated');
        } else {
            this.textElement2_.setAttribute('class', (this as any).className_);
        }
        // Empty the text element.
        goog.dom.removeChildren(/** @type {!Element} */(this.textElement2_));
        // Replace whitespace with non-breaking spaces so the text doesn't collapse.
        text = text.replace(/\s/g, Blockly.Field.NBSP);
        if (this.sourceBlock_.RTL && text) {
            // The SVG is LTR, force text to be RTL.
            text += '\u200F';
        }
        if (!text) {
            // Prevent the field from disappearing if empty.
            text = Blockly.Field.NBSP;
        }
        let textNode = document.createTextNode(text);
        this.textElement2_.appendChild(textNode);

        // Cached width is obsolete.  Clear it.
        this.isDirty_ = true;
    };

    patchDualMotorText(text: string) {
        if (text === null) {
            return text;
        }
        if (text.indexOf('|') == -1) {
            text = this.sourceBlock_.RTL ? `${text}|${lf("large motors")}` : `${lf("large motors")}|${text}`;
        }
        return text;
    }

    setText(text: string) {
        if (text === null || text === this.text_) {
            // No change if null.
            return;
        }
        text = this.patchDualMotorText(text);
        this.text_ = text;
        this.updateTextNode_();
        this.updateTextNode2_();

        if (this.textElement_) {
            this.textElement_.parentNode.appendChild(this.arrow_);
        }
        if (this.textElement2_) {
            this.textElement2_.parentNode.appendChild(this.arrow2_);
        }
        if (this.sourceBlock_ && (<Blockly.BlockSvg>this.sourceBlock_).rendered) {
            (<Blockly.BlockSvg>this.sourceBlock_).render();
            this.sourceBlock_.bumpNeighbours_();
        }
    }

    positionArrow2(start: number, x: number) {
        if (!this.arrow2_) {
            return 0;
        }

        let addedWidth = 0;
        if (this.sourceBlock_.RTL) {
            (this as any).arrow2X_ = (this as any).arrowSize_ - (Blockly.BlockSvg as any).DROPDOWN_ARROW_PADDING;
            addedWidth = (this as any).arrowSize_ + (Blockly.BlockSvg as any).DROPDOWN_ARROW_PADDING;
        } else {
            (this as any).arrow2X_ = x + (Blockly.BlockSvg as any).DROPDOWN_ARROW_PADDING / 2;
            addedWidth = (this as any).arrowSize_ + (Blockly.BlockSvg as any).DROPDOWN_ARROW_PADDING;
        }
        if (this.box_) {
            // Bump positioning to the right for a box-type drop-down.
            (this as any).arrow2X_ += Blockly.BlockSvg.BOX_FIELD_PADDING;
        }
        (this as any).arrow2X_ += start;
        this.arrow2_.setAttribute('transform',
            'translate(' + (this as any).arrow2X_ + ',' + this.arrowY_ + ')'
        );
        return addedWidth;
    };

    updateSize_() {
        // Calculate width of field
        let width = Blockly.Field.getCachedWidth(this.textElement_);
        let width2 = Blockly.Field.getCachedWidth(this.textElement2_);

        // Add padding to left and right of text.
        if (this.EDITABLE) {
            width += Blockly.BlockSvg.EDITABLE_FIELD_PADDING;
            width2 += Blockly.BlockSvg.EDITABLE_FIELD_PADDING;
        }

        // Adjust width for drop-down arrow.
        this.arrowWidth_ = 0;
        if (this.positionArrow) {
            this.arrowWidth_ = this.positionArrow(width);
            width += this.arrowWidth_;
        }

        // Add padding to any drawn box.
        if (this.box_) {
            width += 2 * Blockly.BlockSvg.BOX_FIELD_PADDING;
        }

        // Adjust width for second drop-down arrow.
        (this as any).arrowWidth2_ = 0;
        if (this.positionArrow2) {
            (this as any).arrowWidth2_ = this.positionArrow2(width + Blockly.BlockSvg.BOX_FIELD_PADDING, width2);
            width2 += (this as any).arrowWidth2_;
        }

        // Add padding to any drawn box.
        if (this.box2_) {
            width2 += 2 * Blockly.BlockSvg.BOX_FIELD_PADDING;
        }

        // Set width of the field.
        this.size_.width = width + Blockly.BlockSvg.BOX_FIELD_PADDING + width2;
        (this as any).width1 = width;
        (this as any).width2 = width2;
    };

    render_() {
        if (this.visible_ && this.textElement_) {
            goog.dom.removeChildren(/** @type {!Element} */(this.textElement_));
            goog.dom.removeChildren(/** @type {!Element} */(this.textElement2_));

            // First dropdown
            // Use one of the following options, medium motor, large motor or large motors (translated)
            const textNode1 = document.createTextNode(this.getFirstValueI11n(<string>this.value_));
            this.textElement_.appendChild(textNode1);

            // Second dropdown, no need to translate. Only port numbers
            if (this.textElement2_) {
                const textNode2 = document.createTextNode(this.getSecondValue(<string>this.value_));
                this.textElement2_.appendChild(textNode2);
            }
            this.updateSize_();

            // Update text centering, based on newly calculated width.
            let centerTextX = ((this as any).width1 - this.arrowWidth_) / 2;
            if (this.sourceBlock_.RTL) {
                centerTextX += this.arrowWidth_;
            }

            // In a text-editing shadow block's field,
            // if half the text length is not at least center of
            // visible field (FIELD_WIDTH), center it there instead,
            // unless there is a drop-down arrow.
            if (this.sourceBlock_.isShadow() && !this.positionArrow) {
                let minOffset = (Blockly.BlockSvg as any).FIELD_WIDTH / 2;
                if (this.sourceBlock_.RTL) {
                    // X position starts at the left edge of the block, in both RTL and LTR.
                    // First offset by the width of the block to move to the right edge,
                    // and then subtract to move to the same position as LTR.
                    let minCenter = (this as any).width1 - minOffset;
                    centerTextX = Math.min(minCenter, centerTextX);
                } else {
                    // (width / 2) should exceed Blockly.BlockSvg.FIELD_WIDTH / 2
                    // if the text is longer.
                    centerTextX = Math.max(minOffset, centerTextX);
                }
            }

            // Apply new text element x position.
            let width = Blockly.Field.getCachedWidth(this.textElement_);
            let newX = centerTextX - width / 2;
            this.textElement_.setAttribute('x', `${newX}`);

            // Update text centering, based on newly calculated width.
            let centerTextX2 = ((this as any).width2 - (this as any).arrowWidth2_) / 2;
            if (this.sourceBlock_.RTL) {
                centerTextX2 += (this as any).arrowWidth2_;
            }

            // In a text-editing shadow block's field,
            // if half the text length is not at least center of
            // visible field (FIELD_WIDTH), center it there instead,
            // unless there is a drop-down arrow.
            if (this.sourceBlock_.isShadow() && !this.positionArrow2) {
                let minOffset = (Blockly.BlockSvg as any).FIELD_WIDTH / 2;
                if (this.sourceBlock_.RTL) {
                    // X position starts at the left edge of the block, in both RTL and LTR.
                    // First offset by the width of the block to move to the right edge,
                    // and then subtract to move to the same position as LTR.
                    let minCenter = (this as any).width2 - minOffset;
                    centerTextX2 = Math.min(minCenter, centerTextX2);
                } else {
                    // (width / 2) should exceed Blockly.BlockSvg.FIELD_WIDTH / 2
                    // if the text is longer.
                    centerTextX2 = Math.max(minOffset, centerTextX2);
                }
            }

            // Apply new text element x position.
            let width2 = Blockly.Field.getCachedWidth(this.textElement2_);
            let newX2 = centerTextX2 - width2 / 2;
            this.textElement2_.setAttribute('x', `${newX2 + (this as any).width1 + Blockly.BlockSvg.BOX_FIELD_PADDING}`);
        }

        // Update any drawn box to the correct width and height.
        if (this.box_) {
            this.box_.setAttribute('width', `${(this as any).width1}`);
            this.box_.setAttribute('height', `${this.size_.height}`);
        }

        // Update any drawn box to the correct width and height.
        if (this.box2_) {
            this.box2_.setAttribute('x', `${(this as any).width1 + Blockly.BlockSvg.BOX_FIELD_PADDING}`);
            this.box2_.setAttribute('width', `${(this as any).width2}`);
            this.box2_.setAttribute('height', `${this.size_.height}`);
        }
    };

    showEditor_(e?: MouseEvent) {
        // If there is an existing drop-down we own, this is a request to hide the drop-down.
        if (Blockly.DropDownDiv.hideIfOwner(this)) {
            return;
        }
        this.isFirst_ = e.clientX - this.getScaledBBox_().left < ((this as any).width1 * (<Blockly.WorkspaceSvg>this.sourceBlock_.workspace).scale);
        // If there is an existing drop-down someone else owns, hide it immediately and clear it.
        Blockly.DropDownDiv.hideWithoutAnimation();
        Blockly.DropDownDiv.clearContent();
        // Populate the drop-down with the icons for this field.
        let dropdownDiv = Blockly.DropDownDiv.getContentDiv();
        let contentDiv = document.createElement('div');
        // Accessibility properties
        contentDiv.setAttribute('role', 'menu');
        contentDiv.setAttribute('aria-haspopup', 'true');
        const foptions = this.getOptions(); // [img info, text]

        let opts = {};
        let conts = {};
        let vals = {};
        // Go through all option values and split them into groups
        for (let opt = 0; opt < foptions.length; opt++) {
            const value = foptions[opt][1];
            const motorValue = value.substring(value.indexOf('.') + 1);
            const typeValue = motorValue.indexOf('large') == 0 ? 'large' : 'medium';
            const portValue = motorValue.indexOf('large') == 0 ? motorValue.substring(5) : motorValue.substring(6);
            const isDual = portValue.length > 1;

            const text = `${typeValue} motor${isDual ? 's' : ''}|${portValue}`;
            const key = `${typeValue} motor${isDual ? 's' : ''}`;
            if (!opts[key]) opts[key] = [];
            opts[key].push(portValue);

            conts[text] = foptions[opt][0];
            vals[text] = value;
        }

        const currentFirst = this.getFirstValue(<string>this.value_);
        //const currentSecond = this.getSecondValue(<string>this.value_);

        let options: string[];
        if (!this.isFirst_) {
            options = opts[currentFirst];
        } else {
            options = Object.keys(opts);
            // Flip the first and second options to make it sorted the way we want it (medium, large, dual)
            if (options.length == 3) {
                options = [lf("medium motor"), lf("large motor"), lf("large motors")];
            } else {
                options.reverse();
            }
        }

        const isFirstUrl = {
            'large motors': FieldMotors.DUAL_MOTORS_DATAURI,
            'large motor': FieldMotors.MOTORS_LARGE_DATAURI,
            'medium motor': FieldMotors.MOTORS_MEDIUM_DATAURI
        }
        const columns = options.length;

        for (let i = 0, option: any; option = options[i]; i++) {
            let text = this.isFirst_ ? option + '|' + (option.indexOf('motors') != -1 ? 'BC' : 'A') : currentFirst + '|' + option;
            text = text.replace(/\xA0/g, ' ');
            const content: any = conts[text];
            const value = vals[text];
            // Icons with the type property placeholder take up space but don't have any functionality
            // Use for special-case layouts
            if (content.type == 'placeholder') {
                let placeholder = document.createElement('span');
                placeholder.setAttribute('class', 'blocklyDropDownPlaceholder');
                placeholder.style.width = content.width + 'px';
                placeholder.style.height = content.height + 'px';
                contentDiv.appendChild(placeholder);
                continue;
            }
            let button = document.createElement('button');
            button.setAttribute('id', ':' + i); // For aria-activedescendant
            button.setAttribute('role', 'menuitem');
            button.setAttribute('class', 'blocklyDropDownButton');
            button.title = this.isFirst_ ? this.getFirstValueI11n(value) : this.getSecondValue(value);
            button.style.width = ((this.itemWidth_) - 8) + 'px';
            button.style.height = ((this.itemWidth_) - 8) + 'px';
            let backgroundColor = this.backgroundColour_;
            if (value == this.getValue()) {
                // This icon is selected, show it in a different colour
                backgroundColor = this.sourceBlock_.getColourTertiary();
                button.setAttribute('aria-selected', 'true');
            }
            button.style.backgroundColor = backgroundColor;
            button.style.borderColor = this.borderColour_;
            Blockly.bindEvent_(button, 'click', this, this.buttonClick_);
            Blockly.bindEvent_(button, 'mouseup', this, this.buttonClick_);
            // These are applied manually instead of using the :hover pseudoclass
            // because Android has a bad long press "helper" menu and green highlight
            // that we must prevent with ontouchstart preventDefault
            Blockly.bindEvent_(button, 'mousedown', button, function (e) {
                this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
                e.preventDefault();
            });
            Blockly.bindEvent_(button, 'mouseover', button, function () {
                this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
                contentDiv.setAttribute('aria-activedescendant', this.id);
            });
            Blockly.bindEvent_(button, 'mouseout', button, function () {
                this.setAttribute('class', 'blocklyDropDownButton');
                contentDiv.removeAttribute('aria-activedescendant');
            });
            let buttonImg = document.createElement('img');
            let imgSrc = content.src;
            if (this.isFirst_) {
                const motorValue = value.substring(value.indexOf('.') + 1);
                // Find out what kind of motor this is based on it's value, possible values: mediumX, largeX, and largeXY
                if (motorValue.indexOf('medium') == 0) imgSrc = isFirstUrl['medium motor'];
                else if (motorValue.length == 6) imgSrc = isFirstUrl['large motor'];
                else imgSrc = isFirstUrl['large motors'];
            }
            buttonImg.src = imgSrc;
            //buttonImg.alt = icon.alt;
            // Upon click/touch, we will be able to get the clicked element as e.target
            // Store a data attribute on all possible click targets so we can match it to the icon.
            button.setAttribute('data-value', value);
            buttonImg.setAttribute('data-value', value);
            button.appendChild(buttonImg);
            contentDiv.appendChild(button);
        }
        contentDiv.style.width = (this.itemWidth_ * columns) + 'px';
        dropdownDiv.appendChild(contentDiv);

        Blockly.DropDownDiv.setColour(this.backgroundColour_, this.borderColour_);

        // Calculate positioning based on the field position.
        let scale = (<Blockly.WorkspaceSvg>this.sourceBlock_.workspace).scale;
        let width = this.isFirst_ ? (this as any).width1 : (this as any).width2;
        let bBox = { width: this.size_.width, height: this.size_.height };
        width *= scale;
        bBox.height *= scale;
        let position = this.fieldGroup_.getBoundingClientRect();
        let leftPosition = this.isFirst_ ? position.left : position.left + (scale * (this as any).width1) + Blockly.BlockSvg.BOX_FIELD_PADDING;
        let primaryX = leftPosition + width / 2;
        let primaryY = position.top + bBox.height;
        let secondaryX = primaryX;
        let secondaryY = position.top;
        // Set bounds to workspace; show the drop-down.
        (Blockly.DropDownDiv as any).setBoundsElement((<Blockly.WorkspaceSvg>this.sourceBlock_.workspace).getParentSvg().parentNode);
        (Blockly.DropDownDiv as any).show(this, primaryX, primaryY, secondaryX, secondaryY,
            this.onHide_.bind(this));

        // Update colour to look selected.
        if (this.isFirst_ && this.box_) {
            this.box_.setAttribute('fill', this.sourceBlock_.getColourTertiary());
        } else if (!this.isFirst_ && this.box2_) {
            this.box2_.setAttribute('fill', this.sourceBlock_.getColourTertiary());
        }
    }

    protected buttonClick_ = function (e: any) {
        let value = e.target.getAttribute('data-value');
        this.setValue(value);
        Blockly.DropDownDiv.hide();
    };


    /**
     * Callback for when the drop-down is hidden.
     */
    protected onHide_() {
        const content = Blockly.DropDownDiv.getContentDiv();
        content.removeAttribute('role');
        content.removeAttribute('aria-haspopup');
        content.removeAttribute('aria-activedescendant');
        (content as HTMLElement).style.width = '';
        if (this.isFirst_ && this.box_) {
            this.box_.setAttribute('fill', this.sourceBlock_.getColour());
        } else if (!this.isFirst_ && this.box2_) {
            this.box2_.setAttribute('fill', this.sourceBlock_.getColour());
        }
    };

    static MOTORS_MEDIUM_DATAURI = 'data:image/png;base64,[encoded data, 8324 characters]';


    static MOTORS_LARGE_DATAURI = 'data:image/png;base64,[encoded data, 7588 characters]';

    static DUAL_MOTORS_DATAURI = 'data:image/png;base64,[encoded data, 12768 characters]';
}