/**
 * Copyright (c) 2017 by Robert S. All rights reserved.
 * 
 * This file is part of the QCAD project.
 *
 * QCAD is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * QCAD is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with QCAD.
 */

/**
 * \defgroup ecma_draw_text TextAlong Drawing Tool
 * \ingroup ecma_draw
 *
 * \brief This module contains the ECMAScript implementation of the text along an entity
 * drawing tool.
 */
include("scripts/Draw/Draw.js");
include("scripts/WidgetFactory.js");
include("scripts/Draw/Text/TextDialog/TextDialog.js");

/**
 * \class TextAlong
 * \brief Insert text along an entity.
 * \ingroup ecma_misc_draw
 */
function TextAlong(guiAction, mode) {
    if (isNull(mode)) {
        mode = TextDialog.Mode.Text;
    }

    Draw.call(this, guiAction);

    this.textData = undefined;
    this.mode = mode;
    this.shape = undefined;
    this.text = undefined;
    this.height = 1.0;
    this.spacing = 0.0;
    this.pos = undefined;
    this.ccw = false;
    this.fit = false;
    this.CharWidths = [];
    this.totalCharWidth = 0.0;

    this.setUiOptions("TextAlong.ui");
}

TextAlong.prototype = new Draw();

TextAlong.State = {
    ChoosingEntity : 0,
    SettingPosition : 1
};

TextAlong.prototype.beginEvent = function() {
    Draw.prototype.beginEvent.call(this);

    var dialog = new TextDialog(this.mode);
    this.textData = dialog.show();
    if (isNull(this.textData)) {
        this.terminate();
        return;
    }

    var optionsToolBar = EAction.getOptionsToolBar();

    var contentsEdit = optionsToolBar.findChild("Contents");
    WidgetFactory.initLineEdit(contentsEdit, false);
    contentsEdit.text = this.textData.getEscapedText();
    var heightEdit = optionsToolBar.findChild("Height");
    var ht = heightEdit.getValue();
    this.textData.setTextHeight(ht);

    // generate list of char widths
    this.getCharWidths();

    this.setState(TextAlong.State.ChoosingEntity);
};

TextAlong.prototype.setState = function(state) {
    Draw.prototype.setState.call(this, state);

    var appWin = RMainWindowQt.getMainWindow();
    this.setCrosshairCursor();

    switch (this.state) {
    case TextAlong.State.ChoosingEntity :
        this.getDocumentInterface().setClickMode(RAction.PickEntity);
        this.setCommandPrompt(qsTr("Select Entity"));
        this.setLeftMouseTip(qsTr("Select Entity"));
        this.setRightMouseTip(EAction.trCancel);
        EAction.showSnapTools();
        break;
    case TextAlong.State.SettingPosition :
        this.getDocumentInterface().setClickMode(RAction.PickCoordinate);
        this.setCommandPrompt(qsTr("Position"));
        this.setLeftMouseTip(qsTr("Position"));
        this.setRightMouseTip(EAction.trCancel);
        EAction.showSnapTools();
        break;
    }
};

TextAlong.prototype.pickEntity = function(event, preview) {
    var di = this.getDocumentInterface();
    var doc = this.getDocument();
    var entityId = event.getEntityId();
    var entity = doc.queryEntity(entityId);
    this.pos = event.getModelPosition();

    if (isNull(entity)) {
        this.shape = undefined;
        return;
    } else {
        if (!preview) {
            this.shape = getClosestSimpleShape(entity, this.pos);
            if (!isNull(this.shape)) {
                var type = this.shape.getShapeType();
                if (type !== RShape.Line && type !== RShape.Arc && type !== RShape.Circle) {
                    EAction.warnNotLineArcCircle();
                    return;
                }

                this.setState(TextAlong.State.SettingPosition);
            }
        }
    }
};

TextAlong.prototype.pickCoordinate = function(event, preview) {
    var di = this.getDocumentInterface();

    this.pos = event.getModelPosition();
    if (!preview) {
        di.setRelativeZero(this.pos);
    }
    this.textData.setAlignmentPoint(this.pos);

    this.text = new RTextEntity(this.getDocument(), this.textData);

    var op = this.getOperation(preview);
    if (preview) {
        di.previewOperation(op);
    }
    else {
        if (!isNull(op)) {
            di.applyOperation(op);
        }
    }
};

TextAlong.prototype.getOperation = function(preview) {
    var di = this.getDocumentInterface();
    var type;

    if (!isNull(this.shape)) {
        type = this.shape.getShapeType();
    } else {
        return undefined;
    }

    var op = undefined;
    var arc;
    switch(type) {
    case RShape.Line:
        op = this.alongLine();
        break;
    case RShape.Arc:
        // 'this.shape' is a pointer to the arc, so get the actual arc object
        arc = this.shape;
        op = this.alongArc(arc);
        break;
    case RShape.Circle:
        // create a circular arc and use 'alongArc'.
        // when using 'fit', add one or more spaces to the start or end of the text
        arc = new RArc(this.shape.getCenter(), this.shape.getRadius(), 0.0, 0.0);
        op = this.alongArc(arc);
        break;
    default :
        break;
    }
    return op;
}

TextAlong.prototype.getCharWidths = function() {
    if (isNull(this.textData)) {
        return;
    }

    this.text = new RTextEntity(this.getDocument(), this.textData);
    var txt = this.text.getPlainText();
    var num = txt.length;
    this.CharWidths = [];
    this.totalCharWidth = 0.0;

    for (var i = 0; i < num; i++) {
        var char = txt[i];
        // A space character is returned as zero width
        if (char === " ") {
            char = "-";
        }
        var td = this.getTextData(char);
        var wid = td.getWidth();
        this.CharWidths.push(wid + this.spacing);
        this.totalCharWidth += wid + this.spacing;
    }
}

TextAlong.prototype.getTextData = function(txt) {
    var td = new RTextData();
    if (this.pos !== undefined) {
        td.setAlignmentPoint(this.pos);
    }
    td.setVAlign(this.textData.getVAlign());
    td.setHAlign(this.textData.getHAlign());
    td.setText(txt);
    td.setTextHeight(this.textData.getTextHeight());
    td.setTextWidth(this.textData.getTextWidth());
    td.setFontName(this.textData.getFontName());
    td.setAngle(this.textData.getAngle());
    td.setBold(this.textData.isBold());
    td.setItalic(this.textData.isItalic());
    td.setLineSpacingFactor(this.textData.getLineSpacingFactor());
    return td;
}

TextAlong.prototype.slotContentsChanged = function(value) {
    if (isNull(this.textData)) {
        return;
    }

    this.textData.setText(value);
    // change list of char widths
    this.getCharWidths();

    this.updatePreview(true);
};

TextAlong.prototype.slotHeightChanged = function(value) {
    if (isNull(this.textData)) {
        return;
    }

    this.height = value;
    this.textData.setTextHeight(this.height);
    // change list of char widths
    this.getCharWidths();

    this.updatePreview(true);
};

TextAlong.prototype.slotSpacingChanged = function(value) {
    this.spacing = value;
    this.getCharWidths();

    this.updatePreview(true);
};

TextAlong.prototype.slotDirectionChanged = function(button) {
    if (!isQObject(button) || button.checked !== true) {
        return;
    }

    if (button.objectName === "Clockwise") {
        this.ccw = false;
    }
    else {
        this.ccw = true;
    }

    this.updatePreview(true);
};

TextAlong.prototype.slotFitChanged = function(value) {
    this.fit = value;

    this.updatePreview(true);
};

TextAlong.prototype.alongLine = function() {
    var ang, len, op, h, v;

    ang = this.shape.getAngle();
    len = this.shape.getLength();
    if (!isNull(this.textData)) {
        if (this.ccw) {
            this.textData.setAngle(ang + Math.PI);
        } else {
            this.textData.setAngle(ang);
        }
    }
    op = new RAddObjectsOperation();
    if (this.fit) {
        var txt = this.text.getPlainText();
        var num = txt.length;
        var firstCharWidth = this.CharWidths[0];
        var lastCharWidth = this.CharWidths[num - 1];
        var dist = (len - firstCharWidth) / (num - 1);

        // now position 'this.pos'
        v = new RVector();
        h = this.textData.getHAlign();
        if (h === RS.HAlignRight) {
            v.setPolar(len, this.textData.getAngle() + Math.PI);
            this.pos = this.pos.operator_add(v);
        } else if (h === RS.HAlignCenter) {
            v.setPolar(len / 2.0, this.textData.getAngle() + Math.PI);
            this.pos = this.pos.operator_add(v);
        }

        for (var i = 0; i < num; i++) {
            var td = this.getTextData(txt[i]);
            var te = new RTextEntity(this.getDocument(), td);

            te.setSimple(true);     // This is required so 'base' and 'bottom' points are correct

            te.setVAlign(this.textData.getVAlign());
            te.setHAlign(RS.HAlignCenter);
            v.setPolar((dist * i) + (firstCharWidth / 2.0), te.getAngle());
            var pt = this.pos.operator_add(v);
            te.setAlignmentPoint(pt);
            op.addObject(te);

        }
    } else {
        op.addObject(this.text);
    }
    return op;
}

TextAlong.prototype.alongArc = function(arc) {
    var ang, op;

    op = new RAddObjectsOperation();
    var txt = this.text.getPlainText();
    var num = txt.length;

    // use distance from 'this.pos' to centre point of arc, as radius
    arc.setRadius(arc.center.getDistanceTo(this.pos));
    var pt = this.pos;
    ang = arc.center.getAngleTo(this.pos);

    var arcrad = arc.getRadius();

    // calculate spacing for 'Fit' option
    if (this.fit) {
        // subtract half the width of first character and last character
        var firstcharAngle = ((this.CharWidths[0]) / arcrad) / 2.0;
        var lastcharAngle = ((this.CharWidths[num - 1]) / arcrad) / 2.0;
        var fitAngle = (arc.getSweep() - firstcharAngle - lastcharAngle) / (num - 1);
    }

    var td = this.getTextData(txt[0]);
    var te = new RTextEntity(this.getDocument(), td);
    var h = te.getHAlign();
    var v = te.getVAlign();

    // set rotation angle for first character
    var rotateAngle, sweep;
    var width = this.totalCharWidth;
    if (this.fit) {
        sweep = arc.getSweep();
    } else {
        sweep = width / arcrad;
    }
    if (h === RS.HAlignLeft) {
        rotateAngle = 0.0;
    } else if (h === RS.HAlignCenter) {
        rotateAngle = sweep / 2.0;
    } else {
        rotateAngle = sweep;
    }

    // get angle for half the first characters width
    // and subtract from rotateAngle
    var halfAngle = ((this.CharWidths[0]) / arcrad) / 2.0;
    rotateAngle -= halfAngle;

    if (this.ccw) {
        rotateAngle = -rotateAngle;
    }

    for (var i = 0; i < num; i++) {
        td = this.getTextData(txt[i]);
        te = new RTextEntity(this.getDocument(), td);
        h = te.getHAlign();
        v = te.getVAlign();

        te.setHAlign(RS.HAlignCenter);
        te.setSimple(true);     // This is required so 'base' and 'bottom' points are correct
        te.setAlignmentPoint(pt);
        if (this.ccw) {
            te.setAngle(ang + (Math.PI / 2.0));
        } else {
            te.setAngle(ang - (Math.PI / 2.0));
        }

        // draw each character at 'this.pos', and rotate to match alignment point
        te.rotate(rotateAngle, arc.center);

        op.addObject(te);

        // adjust point and angle for next character
        // function 'getCharWidths' handles spacing
        var currentcharAngle = (this.CharWidths[i]) / arcrad;
        var nextcharAngle = (this.CharWidths[i + 1]) / arcrad;
        var charAngle = (currentcharAngle / 2.0) + (nextcharAngle / 2.0);

        if (this.ccw) {
            if (this.fit) {
                rotateAngle = rotateAngle + fitAngle;
            } else {
                rotateAngle = rotateAngle + charAngle;
            }
        } else {
            if (this.fit) {
                rotateAngle = rotateAngle - fitAngle;
            } else {
                rotateAngle = rotateAngle - charAngle;
            }
        }
    }

    return op;
}
