import { Point } from "@devexpress/utils/lib/geometry/point";
import { DomUtils } from "@devexpress/utils/lib/utils/dom";
import { Position, TaskTitlePosition } from "../../View/Helpers/Enums";
import { GanttView } from "../../View/GanttView";
import { GridElementInfo } from "../../View/Helpers/GridElementInfo";
import { GridLayoutCalculator } from "../../View/Helpers/GridLayoutCalculator";
import { PdfDependencyLineInfo } from "./DataObjects/DependencyLineInfo";
import { PdfTaskResourcesInfo } from "./DataObjects/TaskResourcesInfo";
import { PdfTaskInfo } from "./DataObjects/TaskInfo";
import { Color } from "./Table/Color";
import { StyleDef } from "./Table/StyleDef";
import { Margin } from "./Table/Margin";
import { CellDef } from "./Table/CellDef";
import { TaskAreaExportHelper } from "./TaskAreaHelper";
import { GanttPdfExportProps } from "./Settings/Props";
import { IGanttExportInfo, IGanttPageExportInfo, IGanttTableExportInfo, IGanttObjectExportInfo, PdfPageTableNames } from "./Interfaces";
import { Size } from "@devexpress/utils/lib/geometry/size";
import { PdfGanttPaginator } from "./Paging/Paginator";
import { ScalingHelper } from "./ScalingHelper";
import { PdfTimeMarkerInfo } from "./DataObjects/TimeMarkerInfo";
import { DataExportMode, ExportMode } from "./Settings/Enums";
import { PredefinedStyles } from "./Table/PredefinedStyles";

export class GanttExportCalculator implements IGanttExportInfo {
    public static _defaultPageMargin: number = 10;
    public static _autoFormatWidthAddStock = 1;

    private _owner: GanttView;
    private _taskAreaHelper: TaskAreaExportHelper;
    private _scalingHelper: ScalingHelper;

    private _chartTableBodyMatrix: Array<Array<CellDef>>;
    private _chartTableScaleTopMatrix: Array<Array<CellDef>>;
    private _chartTableScaleBottomMatrix: Array<Array<CellDef>>;

    private _treeListHeaderMatrix: Array<Array<CellDef>>;
    private _treeListBodyMatrix: Array<Array<CellDef>>;
    private _chartScaleTableStyle: StyleDef;
    private _chartMainTableStyle: StyleDef;
    private _treeListTableStyle: StyleDef;

    private _headerTableTop: number;
    private _mainTableTop: number;
    private _chartLeft: number;
    private _treeListLeft: number;
    private _mainTableHeight: number;
    private _treeListWidth: number;
    private _chartWidth: number;

    private _dataObjectLeftDelta: number;
    private _dataObjectTopDelta: number;

    private _tasksInfo: Array<PdfTaskInfo>;
    private _dependenciesInfo: Array<PdfDependencyLineInfo>;
    private _resourcesInfo: Array<PdfTaskResourcesInfo>;
    private _timeMarkersInfo: Array<PdfTimeMarkerInfo>;
    private _props: GanttPdfExportProps;
    private _settingsForPaging: GanttPdfExportProps

    constructor(owner: GanttView, props?: Record<string, any>) {
        this._owner = owner;

        this._props = new GanttPdfExportProps(props);
        this._props.margins ??= new Margin(GanttExportCalculator._defaultPageMargin);
    }
    public get chartTableScaleTopMatrix(): Array<Array<CellDef>> {
        this._chartTableScaleTopMatrix ??= this.calculateChartScaleMatrix(0);
        return this._chartTableScaleTopMatrix;
    }
    public get chartTableScaleBottomMatrix(): Array<Array<CellDef>> {
        this._chartTableScaleBottomMatrix ??= this.calculateChartScaleMatrix(1);
        return this._chartTableScaleBottomMatrix;
    }
    public get chartTableBodyMatrix(): Array<Array<CellDef>> {
        if(!this._chartTableBodyMatrix)
            this.calculateChartTableBodyMatrix();
        return this._chartTableBodyMatrix;
    }
    public get treeListHeaderMatrix(): Array<Array<CellDef>> {
        if(!this._treeListHeaderMatrix)
            this.calculateTreeListTableHeaderMatrix();
        return this._treeListHeaderMatrix;
    }
    public get treeListBodyMatrix(): Array<Array<CellDef>> {
        if(!this._treeListBodyMatrix)
            this.calculateTreeListTableBodyMatrix();
        return this._treeListBodyMatrix;
    }
    public getPages(pdfDoc: any): Array<IGanttPageExportInfo> {
        const paginator = new PdfGanttPaginator(pdfDoc, this.settings, this.createGlobalInfo());
        return paginator.getPages();
    }
    public get settings(): GanttPdfExportProps {
        return this.settingsForPaging;
    }
    protected get layoutCalculator(): GridLayoutCalculator {
        return this._taskAreaHelper.layoutCalculator;
    }
    protected get taskAreaHelper(): TaskAreaExportHelper {
        this._taskAreaHelper ??= new TaskAreaExportHelper(this._owner, this._props);
        return this._taskAreaHelper;
    }
    protected get scalingHelper(): ScalingHelper {
        this._scalingHelper ??= new ScalingHelper(this._props?.pdfDoc);
        return this._scalingHelper;
    }
    protected get visibleTaskIndices(): Array<number> {
        return this.taskAreaHelper.visibleTaskIndices;
    }
    protected get baseCellWidth(): number {
        return this.taskAreaHelper.baseCellSize.width;
    }
    protected get baseCellHeight(): number {
        return this.taskAreaHelper.baseCellHeight;
    }
    protected get chartScaleTableStyle(): StyleDef {
        this._chartScaleTableStyle ??= this.getChartScaleTableStyle();
        return this._chartScaleTableStyle;
    }
    protected get chartMainTableStyle(): StyleDef {
        this._chartMainTableStyle ??= this.getChartMainTableStyle();
        return this._chartMainTableStyle;
    }
    protected get treeListTableStyle(): StyleDef {
        if(!this._treeListTableStyle)
            this.calculateTreeListTableStyle();
        return this._treeListTableStyle;
    }
    protected get pageTopMargin(): number {
        return this._props.margins.top;
    }
    protected get pageLeftMargin(): number {
        return this._props.margins.left;
    }
    protected get pageRightMargin(): number {
        return this._props.margins.right;
    }
    protected get pageBottomMargin(): number {
        return this._props.margins.bottom;
    }
    protected get headerTableTop(): number {
        this._headerTableTop ??= this.pageTopMargin;
        return this._headerTableTop;
    }
    protected get mainTableTop(): number {
        this._mainTableTop ??= this.getMainTableTop();
        return this._mainTableTop;
    }
    protected get exportDataMode(): DataExportMode {
        return this._props.exportDataMode;
    }
    protected getMainTableTop(): number {
        return this.headerTableTop + this.headerTableHeight - this.taskAreaHelper.offsetTop;
    }
    protected get chartLeft(): number {
        this._chartLeft ??= this.getChartLeft();
        return this._chartLeft;
    }
    protected getChartLeft(): number {
        const mode = this._props?.exportMode || ExportMode.all;
        const visibleLeft = mode === ExportMode.chart ? this.pageLeftMargin : this.treeListLeft + this.treeListWidth;
        const left = visibleLeft - this.taskAreaHelper.offsetLeft;
        return left;
    }
    protected get treeListLeft(): number {
        this._treeListLeft ??= this.pageLeftMargin;
        return this._treeListLeft;
    }
    protected get headerTableHeight(): number {
        return 2 * this.taskAreaHelper.headerRowHeight;
    }
    protected get mainTableHeight(): number {
        this._mainTableHeight ??= this.taskAreaHelper.taskAreaHeight;
        return this._mainTableHeight;
    }
    protected get treeListWidth(): number {
        this._treeListWidth ??= this.getTreeListTableWidth();
        return this._treeListWidth;
    }
    protected getTreeListTableWidth(): number {
        const headerWidths = this.treeListHeaderMatrix[0].map((c, i) => this.getTreeListColumnWidth(i));
        return headerWidths?.reduce((acc, v) => acc += v, 0) ?? 0;
    }
    protected get chartWidth(): number {
        if(!this._chartWidth) {
            const row = this.chartTableScaleBottomMatrix[0];
            this._chartWidth = row.reduce((acc, v) =>
                acc += v.styles.cellWidth.hasValue() ? v.styles.cellWidth.getValue() as number : this.baseCellWidth
            , 0);
        }
        return this._chartWidth;
    }
    protected get settingsForPaging(): GanttPdfExportProps {
        if(!this._settingsForPaging) {
            this._settingsForPaging = new GanttPdfExportProps(this._props);
            this.prepareAutoFormat(this._settingsForPaging);
            this.scalingHelper.scalePageMargins(this._settingsForPaging);
        }
        return this._settingsForPaging;
    }
    prepareAutoFormat(settings: GanttPdfExportProps): void {
        if(settings.format === GanttPdfExportProps.autoFormatKey) {
            settings.format = null;
            const landscape = settings.landscape;
            const width = this.autoFormatWidth;
            const height = this.autoFormatHeight;
            const rotateLanscape = (landscape && height > width) || (!landscape && height < width); 
            if(rotateLanscape)
                settings.landscape = !landscape;
            settings.pageSize = new Size(width, height);
        }
    }
    protected get autoFormatWidth(): number {
        const mode = this._props?.exportMode || ExportMode.all;
        const hasChart = mode !== ExportMode.treeList;
        let width = this.pageRightMargin;
        if(hasChart)
            width += this.chartLeft + this.chartWidth;
        else
            width += this.treeListLeft + this.treeListWidth;
        return width + GanttExportCalculator._autoFormatWidthAddStock;
    }
    protected get autoFormatHeight(): number {
        return this.mainTableTop + this.mainTableHeight + this.pageBottomMargin;
    }
    protected createGlobalInfo(): IGanttPageExportInfo {
        const info: IGanttPageExportInfo = {
            objects: this._canExportChart() ? this.getGanttObjectsInfo() : null,
            tables: this.getTablesInfo()
        };
        this.scalingHelper.scaleSizes(info);
        return info;
    }
    protected getTablesInfo(): Record<string, IGanttTableExportInfo> {
        const info = { };
        if(this._canExportTreelist()) {
            info[PdfPageTableNames.treeListHeader] = this.createTreeListHeaderTableInfo();
            info[PdfPageTableNames.treeListMain] = this.createTreeListMainTableInfo();
        }
        if(this._canExportChart()) {
            info[PdfPageTableNames.chartMain] = this.createChartMainTableInfo();
            info[PdfPageTableNames.chartScaleTop] = this._createChartScaleTopInfo();
            info[PdfPageTableNames.chartScaleBottom] = this._createChartScaleBottomInfo();
        }
        return info;
    }
    private get exportMode(): ExportMode {
        return this._props?.exportMode ?? ExportMode.all;
    }
    private _canExportTreelist(): boolean {
        return this.exportMode === ExportMode.all || this.exportMode === ExportMode.treeList;
    }
    private _canExportChart(): boolean {
        return this.exportMode === ExportMode.all || this.exportMode === ExportMode.chart;
    }
    private get _predefinedFont(): string {
        const font = this._props?.pdfDoc?.getFont();
        return font?.fontName || this._props?.font?.name;
    }
    private _createChartScaleTopInfo(): IGanttTableExportInfo {
        return {
            name: PdfPageTableNames.chartScaleTop,
            size: new Size(this.chartWidth, this.taskAreaHelper.headerRowHeight),
            position: new Point(this.chartLeft, this.headerTableTop),
            style: this.chartScaleTableStyle,
            baseCellSize: new Size(this.baseCellWidth, this.taskAreaHelper.headerRowHeight),
            cells: this.chartTableScaleTopMatrix
        };
    }
    private _createChartScaleBottomInfo(): IGanttTableExportInfo {
        const rowHeight = this.taskAreaHelper.headerRowHeight;
        return {
            name: PdfPageTableNames.chartScaleBottom,
            size: new Size(this.chartWidth, rowHeight),
            position: new Point(this.chartLeft, this.headerTableTop + rowHeight),
            style: this.chartScaleTableStyle,
            baseCellSize: new Size(this.baseCellWidth, rowHeight),
            cells: this.chartTableScaleBottomMatrix
        };
    }

    protected createChartMainTableInfo(): IGanttTableExportInfo {
        const info: IGanttTableExportInfo = {
            name: PdfPageTableNames.chartMain,
            size: new Size(this.chartWidth, this.mainTableHeight),
            position: new Point(this.chartLeft, this.mainTableTop),
            style: this.chartMainTableStyle,
            baseCellSize: new Size(this.baseCellWidth, this.baseCellHeight),
            cells: this.chartTableBodyMatrix,
            hideRowLines: !this._owner.settings.areHorizontalBordersEnabled
        };
        return info;
    }
    protected createTreeListHeaderTableInfo(): IGanttTableExportInfo {
        const info: IGanttTableExportInfo = {
            name: PdfPageTableNames.treeListHeader,
            size: new Size(this.treeListWidth, this.headerTableHeight),
            position: new Point(this.treeListLeft, this.headerTableTop),
            style: this.treeListTableStyle,
            baseCellSize: new Size(null, this.headerTableHeight),
            cells: this.treeListHeaderMatrix
        };
        return info;
    }
    protected createTreeListMainTableInfo(): IGanttTableExportInfo {
        const info: IGanttTableExportInfo = {
            name: PdfPageTableNames.treeListMain,
            size: new Size(this.treeListWidth, this.mainTableHeight),
            position: new Point(this.treeListLeft, this.mainTableTop),
            style: this.treeListTableStyle,
            baseCellSize: new Size(null, this.baseCellHeight),
            cells: this.treeListBodyMatrix,
            hideRowLines: !this._owner.settings.areHorizontalBordersEnabled
        };
        return info;
    }
    protected calculateChartScaleMatrix(index: number): Array<Array<CellDef>> {
        const helper = this.taskAreaHelper;
        const ranges = helper.scaleRanges;
        const row = new Array<CellDef>();
        const start = ranges[index][0];
        const end = ranges[index][1];
        for(let j = start; j <= end; j++) {
            const start = this.layoutCalculator.getScaleItemStart(j, helper.scales[index]);
            const cell = new CellDef(this._owner.renderHelper.getScaleItemTextByStart(start, helper.scales[index]));
            cell.styles.cellPadding.assign(0);
            cell.styles.minCellHeight = this.taskAreaHelper.headerRowHeight;
            const cellWidth = index === 0 ? helper.scaleTopWidths[j] : helper.scaleBottomWidths[j];
            cell.styles.cellWidth.assign(cellWidth);
            row.push(cell);
        }
        return [row];
    }

    protected calculateChartTableBodyMatrix(): void {
        this._chartTableBodyMatrix = new Array<Array<CellDef>>();
        if(this.visibleTaskIndices.length > 0)
            this.visibleTaskIndices.forEach(i => this._chartTableBodyMatrix.push(this.createChartTableBodyRow(i)));
        else
            this._chartTableBodyMatrix.push(this.createChartTableBodyRow(-1));
    }
    protected createChartTableBodyRow(index: number): Array<CellDef> {
        const etalon = new CellDef();
        if(this.rowHasChildren(index))
            etalon.styles.fillColor.assign(this.taskAreaHelper.parentRowBackColor);

        return this.chartTableScaleBottomMatrix[0].map((c) => {
            const cell = new CellDef(etalon);
            cell.styles.cellWidth.assign(c.styles.cellWidth);
            return cell;
        });
    }
    protected rowHasSelection(index: number): boolean {
        return this._owner.rowHasSelection(index);
    }
    protected rowHasChildren(index: number): boolean {
        return this._owner.rowHasChildren(index);
    }

    protected calculateTreeListTableHeaderMatrix(): void {
        this._treeListHeaderMatrix = new Array<Array<CellDef>>();

        const owner = this._owner;
        const colCount = owner.getTreeListColCount();
        const row = new Array<CellDef>();
        for(let j = 0; j < colCount; j++) {
            const cell = new CellDef(owner.getTreeListHeaderInfo(j));
            cell.styles.minCellHeight = 2 * this.taskAreaHelper.headerRowHeight;
            row.push(cell);
        }
        this._treeListHeaderMatrix.push(row);
    }

    protected calculateTreeListTableBodyMatrix(): void {
        this._treeListBodyMatrix = new Array<Array<CellDef>>();
        const hasTasks = this.visibleTaskIndices.length > 0;
        if(hasTasks)
            this.fillTreeListTableBodyMatrix(this._treeListBodyMatrix);
        else
            this.fillTreeListEmptyTableBodyMatrix(this._treeListBodyMatrix);
    }
    protected fillTreeListTableBodyMatrix(matrix: Array<Array<CellDef>>): void {
        const visibleTaskIndices = this.visibleTaskIndices;
        const colCount = this.treeListHeaderMatrix[0].length;
        for(let i = 0; i < visibleTaskIndices.length; i++) {
            const row = new Array<CellDef>();
            const visibleIndex = visibleTaskIndices[i];
            const taskKey = this._owner.getTask(visibleIndex)?.id;
            for(let j = 0; j < colCount; j++) {
                const cell = new CellDef(this._owner.getTreeListCellInfo(visibleIndex, j, taskKey));
                if(!cell.styles.cellWidth.hasValue())
                    cell.styles.cellWidth.assign(this.getTreeListColumnWidth(j));
                if(this.rowHasChildren(visibleTaskIndices[i]))
                    cell.styles.fillColor.assign(this.taskAreaHelper.parentRowBackColor);
                row.push(cell);
            }
            matrix.push(row);
        }
    }
    protected fillTreeListEmptyTableBodyMatrix(matrix: Array<Array<CellDef>>): void {
        const row = new Array<CellDef>();
        const cell = new CellDef(this._owner.getTreeListEmptyDataCellInfo());
        cell.styles.cellWidth.assign(this.treeListWidth);
        cell.styles.halign = PredefinedStyles.horizontalAlign[1];
        cell.styles.valign = PredefinedStyles.verticalAlign[1];
        row.push(cell);
        matrix.push(row);
    }
    protected getTreeListColumnWidth(colIndex: number): number {
        const info = this.treeListHeaderMatrix[0][colIndex];
        const style = info && info.styles;
        const numWidth = style.cellWidth.getValue() as number;
        return numWidth || style.minCellWidth || 0;
    }

    protected getObjectsLeftOffset(isTask: boolean = false): number {
        let offset = this.dataObjectLeftDelta;
        if(!isTask)
            offset += this.taskAreaHelper.customRangeLeftOffset;
        return offset;
    }
    protected get dataObjectLeftDelta(): number {
        this._dataObjectLeftDelta ??= this.getDataObjectLeftDelta();
        return this._dataObjectLeftDelta;
    }
    protected get dataObjectTopDelta(): number {
        this._dataObjectTopDelta ??= this.getDataObjectTopDelta();
        return this._dataObjectTopDelta;
    }
    protected getChartScaleTableStyle(): StyleDef {
        const style = new StyleDef(this.taskAreaHelper.scaleTableStyle);
        if(this._predefinedFont)
            style.font = this._predefinedFont;
        return style;
    }
    protected getChartMainTableStyle(): StyleDef {
        const style = new StyleDef(this.taskAreaHelper.chartMainTableStyle);
        if(this._predefinedFont)
            style.font = this._predefinedFont;
        return style;
    }
    protected calculateTreeListTableStyle(): void {
        this._treeListTableStyle = new StyleDef(this._owner.getTreeListTableStyle());
        this._treeListTableStyle.fillColor.assign(this.chartMainTableStyle.fillColor);
        this._treeListTableStyle.lineColor.assign(this.chartMainTableStyle.lineColor);
        if(this._predefinedFont)
            this._treeListTableStyle.font = this._predefinedFont;
    }
    public getGanttObjectsInfo(): IGanttObjectExportInfo {
        return {
            tasks: this.tasksInfo,
            dependencies: this.dependenciesInfo,
            resources: this.resourcesInfo,
            timeMarkers: this.timeMarkersInfo
        };
    }

    public get tasksInfo(): Array<PdfTaskInfo> {
        this._tasksInfo ??= this.calculateTasksInfo();
        return this._tasksInfo;
    }
    public get dependenciesInfo(): Array<PdfDependencyLineInfo> {
        this._dependenciesInfo ??= this.calculateDependenciesInfo();
        return this._dependenciesInfo;
    }
    public get resourcesInfo(): Array<PdfTaskResourcesInfo> {
        this._resourcesInfo ??= this.calculateResourcesInfo();
        return this._resourcesInfo;
    }
    public get timeMarkersInfo(): Array<PdfTimeMarkerInfo> {
        this._timeMarkersInfo ??= this.calculateTimeMarkersInfoInfo();
        return this._timeMarkersInfo;
    }

    protected getDataObjectLeftDelta(): number {
        return this.chartLeft - this.taskAreaHelper.objectsLeftDelta;
    }
    protected getDataObjectTopDelta(): number {
        return this.headerTableTop + this.headerTableHeight - this.taskAreaHelper.objectsTopDelta;
    }

    calculateTasksInfo(): Array<PdfTaskInfo> {
        const result = new Array<PdfTaskInfo>();
        this.visibleTaskIndices.forEach(i => result.push(this.calculateTaskInfo(i)));
        return result;
    }
    calculateTaskInfo(index: number): PdfTaskInfo {
        const info = new PdfTaskInfo();
        const taskElementInfo = this.layoutCalculator.getTaskElementInfo(index);
        info.taskColor = this.getTaskColor(index);
        info.sidePoints = this.getTaskSidePoints(index);

        info.isMilestone = taskElementInfo.className.indexOf(GridLayoutCalculator.milestoneClassName) > 0;
        if(!info.isMilestone) {
            info.isSmallTask = taskElementInfo.className.indexOf(GridLayoutCalculator.smallTaskClassName) > 0;
            info.isParent = taskElementInfo.className.indexOf(GridLayoutCalculator.parentTaskClassName) > 0;
            this.appendTaskTitle(info, index);
            this.appendTaskProgress(info, index);
        }
        return info;
    }
    protected appendTaskTitle(info: PdfTaskInfo, index: number): void {
        const textPosition = this._owner.settings.taskTitlePosition;
        const isTextHidden = info.isSmallTask && textPosition !== TaskTitlePosition.Outside || textPosition === TaskTitlePosition.None;
        if(!isTextHidden) {
            info.text = this._owner.getTaskText(index);
            info.textPosition = textPosition;
            info.textStyle = this.getTaskTextStyle(index);
        }
    }
    protected appendTaskProgress(info: PdfTaskInfo, index: number): void {
        const progressInfo = this.layoutCalculator.getTaskProgressElementInfo(index);
        info.progressWidth = progressInfo.size.width;
        info.progressColor = this.getTaskProgressColor(index);
        info.progressColor.applyOpacityToBackground(info.taskColor);
    }
    protected getTaskSidePoints(index: number): Array<Point> {
        const points = this.layoutCalculator.getTaskSidePoints(index);
        points.forEach(p => {
            p.x += this.getObjectsLeftOffset(true);
            p.y += this.dataObjectTopDelta;
        });
        return points;
    }
    protected getTaskColor(index: number): Color {
        const color = this.taskAreaHelper.getTaskElementBackColor(index, GridLayoutCalculator.taskClassName);
        return new Color(color);
    }
    protected getTaskProgressColor(index: number): Color {
        return new Color(this.taskAreaHelper.getTaskElementBackColor(index, GridLayoutCalculator.taskProgressClassName));
    }
    getTaskTextStyle(index: number): StyleDef {
        const style = new StyleDef();
        style.cellPadding.assign(0);
        style.assign(this.taskAreaHelper.getTaskElementStyle(index, GridLayoutCalculator.taskTitleClassName));
        return style;
    }
    calculateDependenciesInfo(): Array<PdfDependencyLineInfo> {
        const result = new Array<PdfDependencyLineInfo>();
        const helper = this.taskAreaHelper;
        const fillColor = new Color(helper.dependencyColor);
        helper.connectorLines.forEach(line => result.push(this.createLineInfo(line, fillColor, helper.arrowWidth)));
        return result;
    }
    protected createLineInfo(line: GridElementInfo, fillColor: Color, arrowWidth: number): PdfDependencyLineInfo {
        const info = new PdfDependencyLineInfo();
        info.fillColor = fillColor;

        const isArrow = line.className.indexOf(GridLayoutCalculator.arrowClassName) > -1;
        if(isArrow) {
            const position = this.layoutCalculator.getArrowPositionByClassName(line.className);
            info.arrowInfo = { position: position, width: arrowWidth };
            info.points = [ this.getArrowTopCorner(line, position, arrowWidth) ];
        }
        else
            info.points = this.getLinePoints(line);
        return info;
    }
    protected getArrowTopCorner(info: GridElementInfo, position: Position, width: number): Point {
        let x = info.position.x + this.getObjectsLeftOffset();
        let y = info.position.y + this.dataObjectTopDelta;
        switch(position) {
            case Position.Left:
                x += width;
                break;
            case Position.Top:
                y += width;
        }
        return new Point(x, y);
    }
    protected getLinePoints(line: GridElementInfo): Array<Point> {
        const x1 = line.position.x + this.getObjectsLeftOffset();
        const y1 = line.position.y + this.dataObjectTopDelta;
        const x2 = x1 + line.size.width;
        const y2 = y1 + line.size.height;
        return [ new Point(x1, y1), new Point(x2, y2) ];
    }
    calculateResourcesInfo(): Array<PdfTaskResourcesInfo> {
        let result = new Array<PdfTaskResourcesInfo>();
        this.taskAreaHelper.resourcesElements.forEach(rw => result = result.concat(this.calculateResourcesInLine(rw)));
        return result;
    }
    protected calculateResourcesInLine(wrapper: HTMLElement): Array<PdfTaskResourcesInfo> {
        const result = new Array<PdfTaskResourcesInfo>();
        if(wrapper) {
            let left = DomUtils.pxToInt(wrapper.style.left) + this.getObjectsLeftOffset();
            const top = DomUtils.pxToInt(wrapper.style.top) + this.dataObjectTopDelta;

            const resources = wrapper.getElementsByClassName(GridLayoutCalculator.taskResourceClassName);
            for(let i = 0; i < resources.length; i++) {
                const resource = resources[i];
                const style = getComputedStyle(resource);
                left += this.getMargin(style).left;
                result.push(new PdfTaskResourcesInfo(resource.textContent, new StyleDef(style), left, top));
                left += DomUtils.pxToInt(style.width);
            }
        }
        return result;
    }
    calculateTimeMarkersInfoInfo(): Array<PdfTimeMarkerInfo> {
        const result = new Array<PdfTimeMarkerInfo>();
        const stripLines = this.taskAreaHelper.stripLinesElements;
        stripLines.forEach(s => result.push(this.createTimeMarkerInfo(s, true)));
        const noWorkIntervals = this.taskAreaHelper.noWorkingIntervalsElements;
        noWorkIntervals.forEach(s => result.push(this.createTimeMarkerInfo(s, false)));
        return result;
    }
    protected createTimeMarkerInfo(element: HTMLElement, isStripLine: boolean): PdfTimeMarkerInfo {
        const style = getComputedStyle(element);
        const left = DomUtils.pxToInt(style.left) + this.getObjectsLeftOffset();
        const top = DomUtils.pxToInt(style.top) + this.dataObjectTopDelta;
        const width = DomUtils.pxToInt(style.width);
        const height = DomUtils.pxToInt(style.height);
        return new PdfTimeMarkerInfo(new Point(left, top), new Size(width, height), new Color(style.backgroundColor), new Color(style.borderLeftColor), isStripLine);
    }
    protected getMargin(style: CSSStyleDeclaration): Margin {
        const margin = new Margin(0);
        if(style) {
            let marginCss = style.margin;
            if(!marginCss) {
                marginCss += style.marginTop || "0";
                marginCss += " " + style.marginRight || "0";
                marginCss += " " + style.marginBottom || "0";
                marginCss += " " + style.marginLeft || "0";
            }
            margin.assign(marginCss);
        }
        return margin;
    }
}

