/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Views
 */
import { JsonUtils } from "@itwin/core-bentley";
import { Angle, AngleSweep, Arc3d, Matrix3d, Point2d, Point3d, Transform, Vector3d, YawPitchRollAngles } from "@itwin/core-geometry";
import { BisCodeSpec, Code, ColorDef, IModel, LinePixels, Npc, } from "@itwin/core-common";
import { ElementState } from "./EntityState";
import { CoordSystem } from "./CoordSystem";
import { GraphicType } from "./common/render/GraphicType";
/**
 * @public
 * @extensions
 */
export var ACSType;
(function (ACSType) {
    ACSType[ACSType["None"] = 0] = "None";
    ACSType[ACSType["Rectangular"] = 1] = "Rectangular";
    ACSType[ACSType["Cylindrical"] = 2] = "Cylindrical";
    ACSType[ACSType["Spherical"] = 3] = "Spherical";
})(ACSType || (ACSType = {}));
/**
 * @public
 * @extensions
 */
export var ACSDisplayOptions;
(function (ACSDisplayOptions) {
    ACSDisplayOptions[ACSDisplayOptions["None"] = 0] = "None";
    ACSDisplayOptions[ACSDisplayOptions["Active"] = 1] = "Active";
    ACSDisplayOptions[ACSDisplayOptions["Deemphasized"] = 2] = "Deemphasized";
    ACSDisplayOptions[ACSDisplayOptions["Hilite"] = 4] = "Hilite";
    ACSDisplayOptions[ACSDisplayOptions["CheckVisible"] = 8] = "CheckVisible";
    ACSDisplayOptions[ACSDisplayOptions["Dynamics"] = 16] = "Dynamics";
})(ACSDisplayOptions || (ACSDisplayOptions = {}));
/** The state of an AuxCoordSystem element in the frontend
 * @public
 * @extensions
 */
export class AuxCoordSystemState extends ElementState {
    static get className() { return "AuxCoordSystem"; }
    static fromProps(props, iModel) {
        const name = props.classFullName.toLowerCase();
        if (name.endsWith("system2d"))
            return new AuxCoordSystem2dState(props, iModel);
        if (name.endsWith("system3d"))
            return new AuxCoordSystem3dState(props, iModel);
        return new AuxCoordSystemSpatialState(props, iModel);
    }
    /** Create a new AuxCoordSystemState.
     * @param acsName the name for the new AuxCoordSystem
     * @param iModel the iModel for which the ACS applies.
     * @note call this method with the appropriate subclass (e.g. AuxCoordSystemSpatialState, AuxCoordSystem2dState, etc), not on AuxCoordSystemState directly
     */
    static createNew(acsName, iModel) {
        const myCode = new Code({ spec: BisCodeSpec.auxCoordSystemSpatial, scope: IModel.dictionaryId.toString(), value: acsName });
        return new AuxCoordSystemSpatialState({ model: IModel.dictionaryId, code: myCode, classFullName: this.classFullName }, iModel);
    }
    constructor(props, iModel) {
        super(props, iModel);
        this.type = JsonUtils.asInt(props.type, ACSType.None);
        this.description = props.description;
    }
    toJSON() {
        const val = super.toJSON();
        val.type = this.type;
        val.description = this.description;
        return val;
    }
    isValidForView(view) {
        if (view.isSpatialView())
            return this instanceof AuxCoordSystemSpatialState;
        return (view.is3d() === this.is3d);
    }
    get is3d() { return this instanceof AuxCoordSystem3dState; }
    drawGrid(context) {
        // Called for active ACS when grid orientation is GridOrientationType::ACS.
        const view = context.viewport.view;
        const fixedRepsAuto = Point2d.create(); // limit grid to project extents
        context.drawStandardGrid(this.getOrigin(), this.getRotation(), view.getGridSpacing(), view.getGridsPerRef(), false, fixedRepsAuto);
    }
    /** Returns the value, clamped to the supplied range. */
    static limitRange(min, max, val) { return Math.max(min, Math.min(max, val)); }
    /**
     * Given an origin point, returns whether the point falls within the view or not. If adjustOrigin is set to true, a point outside
     * the view will be modified to fall within the appropriate range.
     */
    static isOriginInView(drawOrigin, viewport, adjustOrigin) {
        const testPtView = viewport.worldToView(drawOrigin);
        const frustum = viewport.getFrustum(CoordSystem.View);
        const screenRange = Point3d.create();
        screenRange.x = frustum.points[Npc._000].distance(frustum.points[Npc._100]);
        screenRange.y = frustum.points[Npc._000].distance(frustum.points[Npc._010]);
        screenRange.z = frustum.points[Npc._000].distance(frustum.points[Npc._001]);
        // Check if current acs origin is outside view...
        const inView = (!((testPtView.x < 0 || testPtView.x > screenRange.x) || (testPtView.y < 0 || testPtView.y > screenRange.y)));
        if (!adjustOrigin)
            return inView;
        if (!inView) {
            const offset = viewport.pixelsFromInches(0.6 /* ACSDisplaySizes.TriadSizeInches */);
            testPtView.x = AuxCoordSystemState.limitRange(offset, screenRange.x - offset, testPtView.x);
            testPtView.y = AuxCoordSystemState.limitRange(offset, screenRange.y - offset, testPtView.y);
        }
        // Limit point to NPC box to prevent triad from being clipped from display...
        const originPtNpc = viewport.viewToNpc(testPtView);
        originPtNpc.x = AuxCoordSystemState.limitRange(0, 1, originPtNpc.x);
        originPtNpc.y = AuxCoordSystemState.limitRange(0, 1, originPtNpc.y);
        originPtNpc.z = AuxCoordSystemState.limitRange(0, 1, originPtNpc.z);
        viewport.npcToView(originPtNpc, testPtView);
        viewport.viewToWorld(testPtView, drawOrigin);
        return inView;
    }
    getAdjustedColor(inColor, isFill, viewport, options) {
        let color;
        if ((options & ACSDisplayOptions.Hilite) !== ACSDisplayOptions.None) {
            color = viewport.hilite.color;
        }
        else if ((options & ACSDisplayOptions.Active) !== ACSDisplayOptions.None) {
            color = inColor.equals(ColorDef.white) ? viewport.getContrastToBackgroundColor() : inColor;
        }
        else {
            color = ColorDef.from(150, 150, 150, 0);
        }
        color = color.adjustedForContrast(viewport.view.backgroundColor);
        if (isFill)
            color = color.withTransparency((options & (ACSDisplayOptions.Deemphasized | ACSDisplayOptions.Dynamics)) !== ACSDisplayOptions.None ? 225 : 200);
        else
            color = color.withTransparency((options & ACSDisplayOptions.Deemphasized) !== ACSDisplayOptions.None ? 150 : 75);
        return color;
    }
    addAxisLabel(builder, axis, options, vp) {
        const color = ColorDef.white;
        const lineColor = this.getAdjustedColor(color, false, vp, options);
        builder.setSymbology(lineColor, lineColor, 2);
        const linePts1 = [];
        if (0 === axis) {
            linePts1[0] = Point3d.create(0.4 /* ACSDisplaySizes.LabelStart */, -0.15 /* ACSDisplaySizes.LabelWidth */);
            linePts1[1] = Point3d.create(0.8 /* ACSDisplaySizes.LabelEnd */, 0.15 /* ACSDisplaySizes.LabelWidth */);
        }
        else {
            linePts1[0] = Point3d.create(0.0, 0.4 /* ACSDisplaySizes.LabelStart */);
            linePts1[1] = Point3d.create(0.0, (0.4 /* ACSDisplaySizes.LabelStart */ + 0.8 /* ACSDisplaySizes.LabelEnd */) * 0.5);
        }
        builder.addLineString(linePts1);
        const linePts2 = []; // NOTE: Don't use same point array, addPointString/addLineString don't deep copy...
        if (0 === axis) {
            linePts2[0] = Point3d.create(0.4 /* ACSDisplaySizes.LabelStart */, 0.15 /* ACSDisplaySizes.LabelWidth */);
            linePts2[1] = Point3d.create(0.8 /* ACSDisplaySizes.LabelEnd */, -0.15 /* ACSDisplaySizes.LabelWidth */);
        }
        else {
            linePts2[0] = Point3d.create(0.15 /* ACSDisplaySizes.LabelWidth */, 0.8 /* ACSDisplaySizes.LabelEnd */);
            linePts2[1] = Point3d.create(0.0, (0.4 /* ACSDisplaySizes.LabelStart */ + 0.8 /* ACSDisplaySizes.LabelEnd */) * 0.5);
            linePts2[2] = Point3d.create(-0.15 /* ACSDisplaySizes.LabelWidth */, 0.8 /* ACSDisplaySizes.LabelEnd */);
        }
        builder.addLineString(linePts2);
    }
    addAxis(builder, axis, options, vp) {
        const color = (0 === axis ? ColorDef.red : (1 === axis ? ColorDef.green : ColorDef.blue));
        const lineColor = this.getAdjustedColor(color, false, vp, options);
        const fillColor = this.getAdjustedColor(color, true, vp, options);
        if (axis === 2) {
            builder.setSymbology(lineColor, lineColor, 6);
            builder.addPointString([Point3d.create(0.0, 0.0, 0.65 /* ACSDisplaySizes.ZAxisLength */)]); // NOTE: ACS origin point will be drawn separately as a pickable world decoration...
            const linePts2 = [Point3d.create(), Point3d.create()]; // NOTE: Don't use same point array, addPointString/addLineString don't deep copy...
            linePts2[1].z = 0.65 /* ACSDisplaySizes.ZAxisLength */;
            builder.setSymbology(lineColor, lineColor, 1, (options & ACSDisplayOptions.Dynamics) === ACSDisplayOptions.None ? LinePixels.Solid : LinePixels.Code2);
            builder.addLineString(linePts2);
            const scale = 0.4 /* ACSDisplaySizes.ArrowTipWidth */ / 2;
            const center = Point3d.create();
            const viewRMatrix = vp.rotation;
            const xVec = viewRMatrix.getRow(0);
            const yVec = viewRMatrix.getRow(1);
            builder.placement.matrix.multiplyTransposeVectorInPlace(xVec);
            builder.placement.matrix.multiplyTransposeVectorInPlace(yVec);
            xVec.normalize(xVec);
            yVec.normalize(yVec);
            const ellipse = Arc3d.createScaledXYColumns(center, Matrix3d.createColumns(xVec, yVec, Vector3d.create()), scale, scale, AngleSweep.createStartEnd(Angle.createRadians(0), Angle.createRadians(Math.PI * 2)));
            builder.addArc(ellipse, false, false);
            builder.setBlankingFill(fillColor);
            builder.addArc(ellipse, true, true);
            return;
        }
        const shapePts = [];
        shapePts[0] = Point3d.create(1.25 /* ACSDisplaySizes.ArrowTipEnd */, 0.0);
        shapePts[1] = Point3d.create(0.75 /* ACSDisplaySizes.ArrowTipFlange */, 0.4 /* ACSDisplaySizes.ArrowTipWidth */);
        shapePts[2] = Point3d.create(0.85 /* ACSDisplaySizes.ArrowTipStart */, 0.2 /* ACSDisplaySizes.ArrowBaseWidth */);
        shapePts[3] = Point3d.create(0.3 /* ACSDisplaySizes.ArrowBaseStart */, 0.2 /* ACSDisplaySizes.ArrowBaseWidth */);
        shapePts[4] = Point3d.create(0.3 /* ACSDisplaySizes.ArrowBaseStart */, -0.2 /* ACSDisplaySizes.ArrowBaseWidth */);
        shapePts[5] = Point3d.create(0.85 /* ACSDisplaySizes.ArrowTipStart */, -0.2 /* ACSDisplaySizes.ArrowBaseWidth */);
        shapePts[6] = Point3d.create(0.75 /* ACSDisplaySizes.ArrowTipFlange */, -0.4 /* ACSDisplaySizes.ArrowTipWidth */);
        shapePts[7] = shapePts[0].clone();
        if (1 === axis)
            shapePts.forEach((tmpPt) => tmpPt.set(tmpPt.y, tmpPt.x));
        builder.setSymbology(lineColor, lineColor, 1, (options & ACSDisplayOptions.Dynamics) === ACSDisplayOptions.None ? LinePixels.Solid : LinePixels.Code2);
        builder.addLineString(shapePts);
        this.addAxisLabel(builder, axis, options, vp);
        builder.setBlankingFill(fillColor);
        builder.addShape(shapePts);
    }
    /** Returns a GraphicBuilder for this AuxCoordSystemState. */
    createGraphicBuilder(context, options) {
        const checkOutOfView = (options & ACSDisplayOptions.CheckVisible) !== ACSDisplayOptions.None;
        const drawOrigin = this.getOrigin();
        if (checkOutOfView && !AuxCoordSystemState.isOriginInView(drawOrigin, context.viewport, true))
            options = options | ACSDisplayOptions.Deemphasized;
        let pixelSize = context.viewport.pixelsFromInches(0.6 /* ACSDisplaySizes.TriadSizeInches */);
        if ((options & ACSDisplayOptions.Deemphasized) !== ACSDisplayOptions.None)
            pixelSize *= 0.8;
        else if ((options & ACSDisplayOptions.Active) !== ACSDisplayOptions.None)
            pixelSize *= 0.9;
        const exaggerate = context.viewport.view.getAspectRatioSkew();
        const scale = context.getPixelSizeAtPoint(drawOrigin) * pixelSize;
        const rMatrix = this.getRotation();
        rMatrix.transposeInPlace();
        rMatrix.scaleColumns(scale, scale / exaggerate, scale, rMatrix);
        const transform = Transform.createRefs(drawOrigin, rMatrix);
        const builder = context.createGraphicBuilder(GraphicType.WorldOverlay, transform);
        const vp = context.viewport;
        this.addAxis(builder, 0, options, vp);
        this.addAxis(builder, 1, options, vp);
        this.addAxis(builder, 2, options, vp);
        return builder;
    }
    display(context, options) {
        const builder = this.createGraphicBuilder(context, options);
        if (undefined !== builder)
            context.addDecorationFromBuilder(builder);
    }
}
/** The state of an AuxCoordSystem2d element in the frontend
 * @public
 * @extensions
 */
export class AuxCoordSystem2dState extends AuxCoordSystemState {
    static get className() { return "AuxCoordSystem2d"; }
    constructor(props, iModel) {
        super(props, iModel);
        this.origin = Point2d.fromJSON(props.origin);
        this.angle = JsonUtils.asDouble(props.angle);
        this._rMatrix = Matrix3d.createRotationAroundVector(Vector3d.unitZ(), Angle.createDegrees(this.angle));
    }
    toJSON() {
        const val = super.toJSON();
        val.origin = this.origin;
        val.angle = this.angle;
        return val;
    }
    getOrigin(result) { return Point3d.createFrom(this.origin, result); }
    setOrigin(val) { this.origin.setFrom(val); }
    getRotation(result) { return this._rMatrix.clone(result); }
    setRotation(val) {
        this._rMatrix.setFrom(val);
        const angle = YawPitchRollAngles.createFromMatrix3d(val);
        this.angle = (undefined !== angle ? angle.yaw.degrees : 0.0);
    }
}
/** The state of an AuxCoordSystem3d element in the frontend
 * @public
 * @extensions
 */
export class AuxCoordSystem3dState extends AuxCoordSystemState {
    static get className() { return "AuxCoordSystem3d"; }
    constructor(props, iModel) {
        super(props, iModel);
        this.origin = Point3d.fromJSON(props.origin);
        this.yaw = JsonUtils.asDouble(props.yaw);
        this.pitch = JsonUtils.asDouble(props.pitch);
        this.roll = JsonUtils.asDouble(props.roll);
        const angles = new YawPitchRollAngles(Angle.createDegrees(this.yaw), Angle.createDegrees(this.pitch), Angle.createDegrees(this.roll));
        this._rMatrix = angles.toMatrix3d();
    }
    toJSON() {
        const val = super.toJSON();
        val.origin = this.origin;
        val.yaw = this.yaw;
        val.pitch = this.pitch;
        val.roll = this.roll;
        return val;
    }
    getOrigin(result) { return Point3d.createFrom(this.origin, result); }
    setOrigin(val) { this.origin.setFrom(val); }
    getRotation(result) { return this._rMatrix.clone(result); }
    setRotation(rMatrix) {
        this._rMatrix.setFrom(rMatrix);
        const angles = YawPitchRollAngles.createFromMatrix3d(rMatrix);
        this.yaw = (undefined !== angles ? angles.yaw.degrees : 0.0);
        this.pitch = (undefined !== angles ? angles.pitch.degrees : 0.0);
        this.roll = (undefined !== angles ? angles.roll.degrees : 0.0);
    }
}
/** The state of an AuxCoordSystemSpatial element in the frontend
 * @public
 * @extensions
 */
export class AuxCoordSystemSpatialState extends AuxCoordSystem3dState {
    static get className() { return "AuxCoordSystemSpatial"; }
}
//# sourceMappingURL=AuxCoordSys.js.map