import { Style, StrokeStyle } from "../Model/Style";
import { RectanglePrimitive } from "./Primitives/RectaglePrimitive";
import { SvgPrimitive } from "./Primitives/Primitive";
import { PathPrimitiveMoveToCommand, PathPrimitiveLineToCommand, PathPrimitive, PathPrimitiveCommand } from "./Primitives/PathPrimitive";
import { PatternPrimitive } from "./Primitives/PatternPrimitive";
import { ClipPathPrimitive } from "./Primitives/ClipPathPrimitive";
import { CanvasManagerBase } from "./CanvasManagerBase";
import { Rectangle } from "@devexpress/utils/lib/geometry/rectangle";
import { Offsets } from "@devexpress/utils/lib/geometry/offsets";
import { Size } from "@devexpress/utils/lib/geometry/size";
import { Point } from "@devexpress/utils/lib/geometry/point";
import { IModelSizeListener, IModelChangesListener } from "../Model/ModelManipulator";
import { ItemChange } from "../Model/ModelChange";
import { IViewChangesListener, AutoZoomMode } from "../Settings";
import { ICanvasViewListener } from "./CanvasViewManager";
import { RenderUtils } from "./Utils";
import { DOMManipulator } from "./DOMManipulator";
import { ColorUtils } from "@devexpress/utils/lib/utils/color";
import { UnitConverter } from "@devexpress/utils/lib/class/unit-converter";

const GRID_PAGES_LINEWIDTH = 2;

export class CanvasPageManager extends CanvasManagerBase implements IModelSizeListener, IModelChangesListener, ICanvasViewListener, IViewChangesListener {
    private backgroundContainer: SVGElement;
    private gridRectElement: SVGElement;
    private gridPatternElement: SVGElement;
    private pagesGridClipPathElement: SVGElement;
    private pagesGridPatternElement: SVGElement;

    private pageSize: Size;
    private gridSize: number;
    private gridVisible: boolean;
    private modelSize: Size;

    private simpleView: boolean;
    private pageColor: number;

    private canvasViewOffset: Point;
    private snapPoint: Point = new Point(0, 0);

    readonly gridPatternId = RenderUtils.generateSvgElementId("gridPattern");
    readonly pagesGridPatternId = RenderUtils.generateSvgElementId("pagesGridPattern");
    readonly pagesGridClipId = RenderUtils.generateSvgElementId("pagesGridClip");

    constructor(parent: SVGElement, settings: ICanvasPageManagerSettings, dom: DOMManipulator, instanceId: string) {
        super(settings.zoomLevel, dom, instanceId);
        this.pageColor = settings.pageColor;
        this.modelSize = settings.modelSize;
        this.simpleView = settings.simpleView;
        this.gridSize = settings.gridSize;
        this.gridVisible = settings.gridVisible;
        this.pageSize = settings.pageLandscape ? new Size(settings.pageSize.height, settings.pageSize.width) : settings.pageSize;
        this.canvasViewOffset = new Point(0, 0);
        this.initContainers(parent);
    }

    private initContainers(parent: SVGElement) {
        this.backgroundContainer = parent;
    }

    public redraw() {
        this.redrawPage(this.pageColor);
        this.redrawGrid();
    }

    private redrawPage(color: number) {
        const style = new Style();
        style["fill"] = ColorUtils.colorToHash(color);
        this.getOrCreateElement("page-bg", new RectanglePrimitive(0, 0, "100%", "100%", style, "page"), this.backgroundContainer);
        this.createTextFloodFilter(this.instanceId, "page-bg-textflood-filter", this.backgroundContainer, color);
    }
    private redrawGrid() {
        this.updateGridElements();
        this.updatePagesGridElements();
    }
    private getGridRectElement(primitive: SvgPrimitive<SVGElement>) {
        if(this.gridRectElement === undefined)
            this.gridRectElement = this.createPrimitiveElement(primitive, this.backgroundContainer);
        return this.gridRectElement;
    }
    private getGridPatternElement(primitive: SvgPrimitive<SVGElement>) {
        if(this.gridPatternElement === undefined)
            this.gridPatternElement = this.createPrimitiveElement(primitive, this.backgroundContainer);
        return this.gridPatternElement;
    }
    private updateGridElements() {
        const gridRectPrimitive = new RectanglePrimitive("0", "0", "100%", "100%", null, "grid", null, element => {
            element.style.setProperty("fill", RenderUtils.getUrlPathById(this.gridPatternId));
        });
        const rectEl = this.getGridRectElement(gridRectPrimitive);
        if(!this.gridVisible)
            rectEl.style.display = "none";
        else {
            rectEl.style.display = "";
            this.changePrimitiveElement(gridRectPrimitive, rectEl);
            const absGridSize = UnitConverter.twipsToPixelsF(this.gridSize) * this.actualZoom;
            const sizes = [0, 1, 2, 3, 4].map(i => Math.round(absGridSize * i));
            const outerPathCommands = [
                new PathPrimitiveMoveToCommand(sizes[4].toString(), "0"),
                new PathPrimitiveLineToCommand(sizes[4].toString(), sizes[4].toString()),
                new PathPrimitiveLineToCommand("0", sizes[4].toString())
            ];
            const innerPathCommands = [];
            for(let i = 1; i <= 3; i++) {
                innerPathCommands.push(new PathPrimitiveMoveToCommand(sizes[i].toString(), "0"));
                innerPathCommands.push(new PathPrimitiveLineToCommand(sizes[i].toString(), sizes[4].toString()));
            }
            for(let i = 1; i <= 3; i++) {
                innerPathCommands.push(new PathPrimitiveMoveToCommand("0", sizes[i].toString()));
                innerPathCommands.push(new PathPrimitiveLineToCommand(sizes[4].toString(), sizes[i].toString()));
            }
            const commonSize = absGridSize * 4;
            const canvasViewOffset = this.simpleView ? this.canvasViewOffset : Point.zero();
            const gridPatternPrimitive = new PatternPrimitive(this.gridPatternId,
                [
                    this.createGridPathPrimitive(outerPathCommands, "grid-outer-line"),
                    this.createGridPathPrimitive(innerPathCommands, "grid-inner-line")
                ],
                this.createGridPatternPrimitivePosition(canvasViewOffset.x, this.snapPoint.x, commonSize),
                this.createGridPatternPrimitivePosition(canvasViewOffset.y, this.snapPoint.y, commonSize),
                commonSize.toString(), commonSize.toString());
            this.changePrimitiveElement(gridPatternPrimitive, this.getGridPatternElement(gridPatternPrimitive));
        }
    }
    private createGridPatternPrimitivePosition(offset: number, coord: number, commonSize: number) : string {
        return (((offset + coord * this.actualZoom) % commonSize - commonSize) % commonSize).toString();
    }
    private createGridPathPrimitive(commands: PathPrimitiveCommand[], className: string) : PathPrimitive {
        return new PathPrimitive(commands, StrokeStyle.default1pxNegativeOffsetInstance, className);
    }
    private getPagesGridRectElement(primitive: SvgPrimitive<SVGElement>) {
        return this.getOrCreateElement("grid-pages-rect", primitive, this.backgroundContainer);
    }
    private getPagesGridClipPathElement(primitive: SvgPrimitive<SVGElement>) {
        if(this.pagesGridClipPathElement === undefined)
            this.pagesGridClipPathElement = this.createPrimitiveElement(primitive, this.backgroundContainer);
        return this.pagesGridClipPathElement;
    }
    private getPagesGridPatternElement(primitive: SvgPrimitive<SVGElement>) {
        if(this.pagesGridPatternElement === undefined)
            this.pagesGridPatternElement = this.createPrimitiveElement(primitive, this.backgroundContainer);
        return this.pagesGridPatternElement;
    }
    private updatePagesGridElements() {
        const pageAbsSize = this.getAbsoluteSize(this.pageSize);
        const rectPrimitive = new RectanglePrimitive("0", "0", "100%", "100%", null, "grid-page", this.pagesGridClipId,
            element => {
                element.style.setProperty("fill", RenderUtils.getUrlPathById(this.pagesGridPatternId));
                element.style.setProperty("display", this.simpleView ? "none" : "");
            }
        );
        this.getPagesGridRectElement(rectPrimitive);
        if(!this.simpleView) {
            const modelSize = this.modelSize.clone().multiply(this.actualZoom, this.actualZoom);
            const pageGridPathCommands = [
                new PathPrimitiveMoveToCommand((pageAbsSize.width - GRID_PAGES_LINEWIDTH / 2).toString(), "0"),
                new PathPrimitiveLineToCommand(
                    (pageAbsSize.width - GRID_PAGES_LINEWIDTH / 2).toString(),
                    (pageAbsSize.height - GRID_PAGES_LINEWIDTH / 2).toString()
                ),
                new PathPrimitiveLineToCommand("0", (pageAbsSize.height - GRID_PAGES_LINEWIDTH / 2).toString())
            ];
            const pagesGridPatternPrimitive = new PatternPrimitive(this.pagesGridPatternId, [
                new PathPrimitive(pageGridPathCommands, null, "pages-grid-line")
            ], 0, 0, pageAbsSize.width.toString(), pageAbsSize.height.toString());
            this.changePrimitiveElement(pagesGridPatternPrimitive, this.getPagesGridPatternElement(pagesGridPatternPrimitive));

            const pagesGridClipPathPrimitive = new ClipPathPrimitive(this.pagesGridClipId, [
                new RectanglePrimitive(0, 0,
                    (UnitConverter.twipsToPixelsF(modelSize.width) - GRID_PAGES_LINEWIDTH * 2).toString(),
                    (UnitConverter.twipsToPixelsF(modelSize.height) - GRID_PAGES_LINEWIDTH * 2).toString())
            ]);
            this.changePrimitiveElement(pagesGridClipPathPrimitive, this.getPagesGridClipPathElement(pagesGridClipPathPrimitive));
        }
    }

    notifyModelSizeChanged(size: Size, offset?: Offsets) {
        this.modelSize = size.clone();
        this.redraw();
    }
    notifyModelRectangleChanged(rectangle: Rectangle) { }
    notifySnapPointPositionChanged(point: Point) {
        this.snapPoint = point.clone().applyConverter(UnitConverter.twipsToPixelsF);
        this.redrawGrid();
    }

    notifyPageColorChanged(color: number) {
        this.pageColor = color;
        this.redrawPage(this.pageColor);
    }
    notifyModelChanged(changes: ItemChange[]) { }

    notifyPageSizeChanged(pageSize: Size, pageLandscape: boolean) {
        this.pageSize = pageLandscape ? new Size(pageSize.height, pageSize.width) : pageSize.clone();
        this.redraw();
    }

    notifyActualZoomChanged(actualZoom: number) {
        this.actualZoom = actualZoom;
        this.redraw();
    }
    notifyViewAdjusted(canvasViewOffset: Point) {
        if(!this.canvasViewOffset.equals(canvasViewOffset)) {
            this.canvasViewOffset = canvasViewOffset;
            if(this.simpleView)
                this.redraw();
        }
    }
    notifyViewChanged(simpleView: boolean) {
        this.simpleView = simpleView;
        this.redraw();
    }
    notifyGridChanged(showGrid: boolean, gridSize: number) {
        this.gridVisible = showGrid;
        this.gridSize = gridSize;
        this.redraw();
    }
}

export interface ICanvasPageManagerSettings {
    modelSize: Size;
    rectangle: Rectangle;
    zoomLevel: number;
    simpleView: boolean;
    autoZoom: AutoZoomMode;
    readOnly: boolean;
    contextMenuEnabled: boolean;
    pageColor: number;
    pageLandscape: boolean;
    pageSize: Size;
    gridVisible: boolean;
    gridSize: number;
}
