/*
 *  Copyright (C) 2010-2021 JPEXS, All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3.0 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.
 */
package com.jpexs.decompiler.flash.exporters.morphshape;

import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
import com.jpexs.decompiler.flash.types.ColorTransform;
import com.jpexs.decompiler.flash.types.FILLSTYLE;
import com.jpexs.decompiler.flash.types.FOCALGRADIENT;
import com.jpexs.decompiler.flash.types.LINESTYLE;
import com.jpexs.decompiler.flash.types.LINESTYLE2;
import com.jpexs.decompiler.flash.types.RGB;
import com.jpexs.decompiler.flash.types.SHAPE;
import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE;
import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD;
import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *
 * @author JPEXS, Claus Wahlers
 */
public abstract class MorphShapeExporterBase implements IMorphShapeExporter {

    protected final SHAPE shape;

    protected final SHAPE shapeEnd;

    protected List<FILLSTYLE> _fillStyles;

    protected List<LINESTYLE> _lineStyles;

    protected List<FILLSTYLE> _fillStylesEnd;

    protected List<LINESTYLE> _lineStylesEnd;

    protected List<Map<Integer, List<IMorphEdge>>> _fillEdgeMaps;

    protected List<Map<Integer, List<IMorphEdge>>> _lineEdgeMaps;

    private boolean edgeMapsCreated;

    protected ColorTransform colorTransform;

    public MorphShapeExporterBase(SHAPE shape, SHAPE endShape, ColorTransform colorTransform) {
        this.shape = shape;
        this.shapeEnd = endShape;
        this.colorTransform = colorTransform;
        _fillStyles = new ArrayList<>();
        _lineStyles = new ArrayList<>();
        if (shape instanceof SHAPEWITHSTYLE) {
            SHAPEWITHSTYLE shapeWithStyle = (SHAPEWITHSTYLE) shape;
            _fillStyles.addAll(Arrays.asList(shapeWithStyle.fillStyles.fillStyles));
            _lineStyles.addAll(Arrays.asList(shapeWithStyle.lineStyles.lineStyles));
        }
        _fillStylesEnd = new ArrayList<>();
        _lineStylesEnd = new ArrayList<>();
        if (endShape instanceof SHAPEWITHSTYLE) {
            SHAPEWITHSTYLE shapeWithStyle = (SHAPEWITHSTYLE) endShape;
            _fillStylesEnd.addAll(Arrays.asList(shapeWithStyle.fillStyles.fillStyles));
            _lineStylesEnd.addAll(Arrays.asList(shapeWithStyle.lineStyles.lineStyles));
        }
    }

    public void export() {
        // Create edge maps
        _fillEdgeMaps = new ArrayList<>();
        _lineEdgeMaps = new ArrayList<>();
        createEdgeMaps(_fillStyles, _lineStyles, _fillStylesEnd, _lineStylesEnd, _fillEdgeMaps, _lineEdgeMaps);

        // Let the doc handler know that a shape export starts
        beginShape();
        // Export fills and strokes for each group separately
        for (int i = 0; i < _lineEdgeMaps.size(); i++) {
            // Export fills first
            exportFillPath(i);
            // Export strokes last
            exportLinePath(i);
        }
        // Let the doc handler know that we're done exporting a shape
        endShape();
    }

    protected void createEdgeMaps(List<FILLSTYLE> fillStyles, List<LINESTYLE> lineStyles,
            List<FILLSTYLE> fillStylesEnd, List<LINESTYLE> lineStylesEnd,
            List<Map<Integer, List<IMorphEdge>>> fillEdgeMaps, List<Map<Integer, List<IMorphEdge>>> lineEdgeMaps) {
        if (!edgeMapsCreated) {
            int xPos = 0;
            int yPos = 0;
            int xPosEnd = 0;
            int yPosEnd = 0;
            int fillStyleIdxOffset = 0;
            int lineStyleIdxOffset = 0;
            int currentFillStyleIdx0 = 0;
            int currentFillStyleIdx1 = 0;
            int currentLineStyleIdx = 0;
            List<IMorphEdge> subPath = new ArrayList<>();
            Map<Integer, List<IMorphEdge>> currentFillEdgeMap = new HashMap<>();
            Map<Integer, List<IMorphEdge>> currentLineEdgeMap = new HashMap<>();
            List<SHAPERECORD> records = shape.shapeRecords;
            List<SHAPERECORD> recordsEnd = shapeEnd.shapeRecords;
            if (records.size() != recordsEnd.size()) {
                throw new Error("Begin and end shaperecord list length should be the same.");
            }
            for (int i = 0; i < records.size(); i++) {
                SHAPERECORD shapeRecord = records.get(i);
                SHAPERECORD shapeRecordEnd = recordsEnd.get(i);
                if ((shapeRecord instanceof StyleChangeRecord && !(shapeRecordEnd instanceof StyleChangeRecord))
                        || (shapeRecord instanceof StraightEdgeRecord && !(shapeRecordEnd instanceof StraightEdgeRecord))
                        || (shapeRecord instanceof CurvedEdgeRecord && !(shapeRecordEnd instanceof CurvedEdgeRecord))
                        || (shapeRecord instanceof EndShapeRecord && !(shapeRecordEnd instanceof EndShapeRecord))) {
                    throw new Error("Begin and end shaperecord should have the same type.");
                }
                if (shapeRecord instanceof StyleChangeRecord) {
                    StyleChangeRecord styleChangeRecord = (StyleChangeRecord) shapeRecord;
                    if (styleChangeRecord.stateLineStyle || styleChangeRecord.stateFillStyle0 || styleChangeRecord.stateFillStyle1) {
                        processSubPath(subPath, currentLineStyleIdx, currentFillStyleIdx0, currentFillStyleIdx1, currentFillEdgeMap, currentLineEdgeMap);
                        subPath = new ArrayList<>();
                    }
                    if (styleChangeRecord.stateNewStyles) {
                        fillStyleIdxOffset = fillStyles.size();
                        lineStyleIdxOffset = lineStyles.size();
                        appendFillStyles(fillStyles, styleChangeRecord.fillStyles.fillStyles);
                        appendLineStyles(lineStyles, styleChangeRecord.lineStyles.lineStyles);
                        appendFillStyles(fillStylesEnd, styleChangeRecord.fillStyles.fillStyles);
                        appendLineStyles(lineStylesEnd, styleChangeRecord.lineStyles.lineStyles);
                    }
                    // Check if all styles are reset to 0.
                    // This (probably) means that a new group starts with the next record
                    if (styleChangeRecord.stateLineStyle && styleChangeRecord.lineStyle == 0
                            && styleChangeRecord.stateFillStyle0 && styleChangeRecord.fillStyle0 == 0
                            && styleChangeRecord.stateFillStyle1 && styleChangeRecord.fillStyle1 == 0) {
                        // do not clean the edges for morphshapes
                        //cleanEdgeMap(currentFillEdgeMap);
                        //cleanEdgeMap(currentLineEdgeMap);
                        fillEdgeMaps.add(currentFillEdgeMap);
                        lineEdgeMaps.add(currentLineEdgeMap);
                        currentFillEdgeMap = new HashMap<>();
                        currentLineEdgeMap = new HashMap<>();
                        currentLineStyleIdx = 0;
                        currentFillStyleIdx0 = 0;
                        currentFillStyleIdx1 = 0;
                    } else {
                        if (styleChangeRecord.stateLineStyle) {
                            currentLineStyleIdx = styleChangeRecord.lineStyle;
                            if (currentLineStyleIdx > 0) {
                                currentLineStyleIdx += lineStyleIdxOffset;
                            }
                        }
                        if (styleChangeRecord.stateFillStyle0) {
                            currentFillStyleIdx0 = styleChangeRecord.fillStyle0;
                            if (currentFillStyleIdx0 > 0) {
                                currentFillStyleIdx0 += fillStyleIdxOffset;
                            }
                        }
                        if (styleChangeRecord.stateFillStyle1) {
                            currentFillStyleIdx1 = styleChangeRecord.fillStyle1;
                            if (currentFillStyleIdx1 > 0) {
                                currentFillStyleIdx1 += fillStyleIdxOffset;
                            }
                        }
                    }
                    if (styleChangeRecord.stateMoveTo) {
                        xPos = styleChangeRecord.moveDeltaX;
                        yPos = styleChangeRecord.moveDeltaY;
                    }

                    StyleChangeRecord styleChangeRecordEnd = (StyleChangeRecord) shapeRecordEnd;
                    if (styleChangeRecordEnd.stateMoveTo) {
                        xPosEnd = styleChangeRecordEnd.moveDeltaX;
                        yPosEnd = styleChangeRecordEnd.moveDeltaY;
                    }
                } else if (shapeRecord instanceof StraightEdgeRecord) {
                    StraightEdgeRecord straightEdgeRecord = (StraightEdgeRecord) shapeRecord;
                    int xPosFrom = xPos;
                    int yPosFrom = yPos;
                    if (straightEdgeRecord.generalLineFlag) {
                        xPos += straightEdgeRecord.deltaX;
                        yPos += straightEdgeRecord.deltaY;
                    } else if (straightEdgeRecord.vertLineFlag) {
                        yPos += straightEdgeRecord.deltaY;
                    } else {
                        xPos += straightEdgeRecord.deltaX;
                    }

                    StraightEdgeRecord straightEdgeRecordEnd = (StraightEdgeRecord) shapeRecordEnd;
                    int xPosEndFrom = xPosEnd;
                    int yPosEndFrom = yPosEnd;
                    if (straightEdgeRecordEnd.generalLineFlag) {
                        xPosEnd += straightEdgeRecordEnd.deltaX;
                        yPosEnd += straightEdgeRecordEnd.deltaY;
                    } else if (straightEdgeRecordEnd.vertLineFlag) {
                        yPosEnd += straightEdgeRecordEnd.deltaY;
                    } else {
                        xPosEnd += straightEdgeRecordEnd.deltaX;
                    }

                    subPath.add(new StraightMorphEdge(xPosFrom, yPosFrom, xPos, yPos, xPosEndFrom, yPosEndFrom, xPosEnd, yPosEnd, currentLineStyleIdx, currentFillStyleIdx1));
                } else if (shapeRecord instanceof CurvedEdgeRecord) {
                    CurvedEdgeRecord curvedEdgeRecord = (CurvedEdgeRecord) shapeRecord;
                    int xPosFrom = xPos;
                    int yPosFrom = yPos;
                    int xPosControl = xPos + curvedEdgeRecord.controlDeltaX;
                    int yPosControl = yPos + curvedEdgeRecord.controlDeltaY;
                    xPos = xPosControl + curvedEdgeRecord.anchorDeltaX;
                    yPos = yPosControl + curvedEdgeRecord.anchorDeltaY;

                    CurvedEdgeRecord curvedEdgeRecordEnd = (CurvedEdgeRecord) shapeRecordEnd;
                    int xPosEndFrom = xPosEnd;
                    int yPosEndFrom = yPosEnd;
                    int xPosEndControl = xPosEnd + curvedEdgeRecordEnd.controlDeltaX;
                    int yPosEndControl = yPosEnd + curvedEdgeRecordEnd.controlDeltaY;
                    xPosEnd = xPosEndControl + curvedEdgeRecordEnd.anchorDeltaX;
                    yPosEnd = yPosEndControl + curvedEdgeRecordEnd.anchorDeltaY;

                    subPath.add(new CurvedMorphEdge(xPosFrom, yPosFrom, xPosControl, yPosControl, xPos, yPos, xPosEndFrom, yPosEndFrom, xPosEndControl, yPosEndControl, xPosEnd, yPosEnd, currentLineStyleIdx, currentFillStyleIdx1));
                } else if (shapeRecord instanceof EndShapeRecord) {
                    // We're done. Process the last subpath, if any
                    processSubPath(subPath, currentLineStyleIdx, currentFillStyleIdx0, currentFillStyleIdx1, currentFillEdgeMap, currentLineEdgeMap);
                    // do not clean the edges for morphshapes
                    //cleanEdgeMap(currentFillEdgeMap);
                    //cleanEdgeMap(currentLineEdgeMap);
                    fillEdgeMaps.add(currentFillEdgeMap);
                    lineEdgeMaps.add(currentLineEdgeMap);
                }
            }
            edgeMapsCreated = true;
        }
    }

    protected void processSubPath(List<IMorphEdge> subPath, int lineStyleIdx, int fillStyleIdx0, int fillStyleIdx1,
            Map<Integer, List<IMorphEdge>> currentFillEdgeMap, Map<Integer, List<IMorphEdge>> currentLineEdgeMap) {
        List<IMorphEdge> path;
        if (fillStyleIdx0 != 0) {
            path = currentFillEdgeMap.get(fillStyleIdx0);
            if (path == null) {
                path = new ArrayList<>();
                currentFillEdgeMap.put(fillStyleIdx0, path);
            }
            for (int j = subPath.size() - 1; j >= 0; j--) {
                path.add(subPath.get(j).reverseWithNewFillStyle(fillStyleIdx0));
            }
        }
        if (fillStyleIdx1 != 0) {
            path = currentFillEdgeMap.get(fillStyleIdx1);
            if (path == null) {
                path = new ArrayList<>();
                currentFillEdgeMap.put(fillStyleIdx1, path);
            }
            appendEdges(path, subPath);
        }
        if (lineStyleIdx != 0) {
            path = currentLineEdgeMap.get(lineStyleIdx);
            if (path == null) {
                path = new ArrayList<>();
                currentLineEdgeMap.put(lineStyleIdx, path);
            }
            appendEdges(path, subPath);
        }
    }

    protected void exportFillPath(int groupIndex) {
        List<IMorphEdge> path = createPathFromEdgeMap(_fillEdgeMaps.get(groupIndex));
        int posX = Integer.MAX_VALUE;
        int posY = Integer.MAX_VALUE;
        int fillStyleIdx = Integer.MAX_VALUE;
        if (path.size() > 0) {
            beginFills();
            for (int i = 0; i < path.size(); i++) {
                IMorphEdge e = path.get(i);
                if (fillStyleIdx != e.getFillStyleIdx()) {
                    if (fillStyleIdx != Integer.MAX_VALUE) {
                        endFill();
                    }
                    fillStyleIdx = e.getFillStyleIdx();
                    posX = Integer.MAX_VALUE;
                    posY = Integer.MAX_VALUE;
                    if (fillStyleIdx - 1 < _fillStyles.size()) {
                        Matrix matrix;
                        Matrix matrixEnd;
                        FILLSTYLE fillStyle = _fillStyles.get(fillStyleIdx - 1);
                        FILLSTYLE fillStyleEnd = _fillStylesEnd.get(fillStyleIdx - 1);
                        switch (fillStyle.fillStyleType) {
                            case FILLSTYLE.SOLID:
                                // Solid fill
                                if (colorTransform == null) {
                                    beginFill(fillStyle.color, fillStyleEnd.color);
                                } else {
                                    beginFill(colorTransform.apply(fillStyle.color), colorTransform.apply(fillStyleEnd.color));
                                }
                                break;
                            case FILLSTYLE.LINEAR_GRADIENT:
                            case FILLSTYLE.RADIAL_GRADIENT:
                            case FILLSTYLE.FOCAL_RADIAL_GRADIENT:
                                // Gradient fill
                                matrix = new Matrix(fillStyle.gradientMatrix);
                                matrixEnd = new Matrix(fillStyleEnd.gradientMatrix);
                                beginGradientFill(
                                        fillStyle.fillStyleType,
                                        colorTransform == null ? fillStyle.gradient.gradientRecords : colorTransform.apply(fillStyle.gradient.gradientRecords),
                                        colorTransform == null ? fillStyleEnd.gradient.gradientRecords : colorTransform.apply(fillStyleEnd.gradient.gradientRecords),
                                        matrix,
                                        matrixEnd,
                                        fillStyle.gradient.spreadMode,
                                        fillStyle.gradient.interpolationMode,
                                        (fillStyle.gradient instanceof FOCALGRADIENT) ? ((FOCALGRADIENT) fillStyle.gradient).focalPoint : 0,
                                        (fillStyleEnd.gradient instanceof FOCALGRADIENT) ? ((FOCALGRADIENT) fillStyleEnd.gradient).focalPoint : 0
                                );
                                break;
                            case FILLSTYLE.REPEATING_BITMAP:
                            case FILLSTYLE.CLIPPED_BITMAP:
                            case FILLSTYLE.NON_SMOOTHED_REPEATING_BITMAP:
                            case FILLSTYLE.NON_SMOOTHED_CLIPPED_BITMAP:
                                // Bitmap fill
                                matrix = new Matrix(fillStyle.bitmapMatrix);
                                matrixEnd = new Matrix(fillStyleEnd.bitmapMatrix);
                                beginBitmapFill(
                                        fillStyle.bitmapId,
                                        matrix,
                                        matrixEnd,
                                        (fillStyle.fillStyleType == FILLSTYLE.REPEATING_BITMAP || fillStyle.fillStyleType == FILLSTYLE.NON_SMOOTHED_REPEATING_BITMAP),
                                        (fillStyle.fillStyleType == FILLSTYLE.REPEATING_BITMAP || fillStyle.fillStyleType == FILLSTYLE.CLIPPED_BITMAP),
                                        colorTransform
                                );
                                break;
                        }
                    } else {
                        // Font shapes define no fillstyles per se, but do reference fillstyle index 1,
                        // which represents the font color. We just report null in this case.
                        beginFill(null, null);
                    }
                }
                if (posX != e.getFromX() || posY != e.getFromY()) {
                    moveTo(e.getFromX(), e.getFromY(), e.getFromEndX(), e.getFromEndY());
                }
                if (e instanceof CurvedMorphEdge) {
                    CurvedMorphEdge c = (CurvedMorphEdge) e;
                    curveTo(c.getControlX(), c.getControlY(), c.toX, c.toY, c.getControlEndX(), c.getControlEndY(), c.toEndX, c.toEndY);
                } else {
                    lineTo(e.getToX(), e.getToY(), e.getToEndX(), e.getToEndY());
                }
                posX = e.getToX();
                posY = e.getToY();
            }
            if (fillStyleIdx != Integer.MAX_VALUE) {
                endFill();
            }
            endFills();
        }
    }

    protected void exportLinePath(int groupIndex) {
        List<IMorphEdge> path = createPathFromEdgeMap(_lineEdgeMaps.get(groupIndex));
        int posX = Integer.MAX_VALUE;
        int posY = Integer.MAX_VALUE;
        int lineStyleIdx = Integer.MAX_VALUE;
        if (path.size() > 0) {
            beginLines();
            for (int i = 0; i < path.size(); i++) {
                IMorphEdge e = path.get(i);
                if (lineStyleIdx != e.getLineStyleIdx()) {
                    lineStyleIdx = e.getLineStyleIdx();
                    posX = Integer.MAX_VALUE;
                    posY = Integer.MAX_VALUE;
                    LINESTYLE lineStyle = null;
                    LINESTYLE lineStyleEnd = null;
                    try {
                        lineStyle = _lineStyles.get(lineStyleIdx - 1);
                        lineStyleEnd = _lineStylesEnd.get(lineStyleIdx - 1);
                    } catch (Exception ex) {
                    }
                    if (lineStyle != null) {
                        String scaleMode = "NORMAL";
                        boolean pixelHintingFlag = false;
                        int startCapStyle = LINESTYLE2.ROUND_CAP;
                        int endCapStyle = LINESTYLE2.ROUND_CAP;
                        int joinStyle = LINESTYLE2.ROUND_JOIN;
                        float miterLimitFactor = 3f;
                        boolean hasFillFlag = false;
                        if (lineStyle instanceof LINESTYLE2) {
                            LINESTYLE2 lineStyle2 = (LINESTYLE2) lineStyle;
                            if (lineStyle2.noHScaleFlag && lineStyle2.noVScaleFlag) {
                                scaleMode = "NONE";
                            } else if (lineStyle2.noHScaleFlag) {
                                scaleMode = "VERTICAL";
                            } else if (lineStyle2.noVScaleFlag) {
                                scaleMode = "HORIZONTAL";
                            }
                            pixelHintingFlag = lineStyle2.pixelHintingFlag;
                            startCapStyle = lineStyle2.startCapStyle;
                            endCapStyle = lineStyle2.endCapStyle;
                            joinStyle = lineStyle2.joinStyle;
                            miterLimitFactor = lineStyle2.miterLimitFactor;
                            hasFillFlag = lineStyle2.hasFillFlag;
                        }
                        lineStyle(
                                lineStyle.width,
                                lineStyleEnd.width,
                                colorTransform == null ? lineStyle.color : colorTransform.apply(lineStyle.color),
                                colorTransform == null ? lineStyleEnd.color : colorTransform.apply(lineStyleEnd.color),
                                pixelHintingFlag,
                                scaleMode,
                                startCapStyle,
                                endCapStyle,
                                joinStyle,
                                miterLimitFactor);

                        if (hasFillFlag) {
                            LINESTYLE2 lineStyle2 = (LINESTYLE2) lineStyle;
                            FILLSTYLE fillStyle = lineStyle2.fillType;
                            LINESTYLE2 lineStyle2End = (LINESTYLE2) lineStyleEnd;
                            FILLSTYLE fillStyleEnd = lineStyle2End.fillType;
                            switch (fillStyle.fillStyleType) {
                                case FILLSTYLE.LINEAR_GRADIENT:
                                case FILLSTYLE.RADIAL_GRADIENT:
                                case FILLSTYLE.FOCAL_RADIAL_GRADIENT:
                                    // Gradient fill
                                    Matrix matrix = new Matrix(fillStyle.gradientMatrix);
                                    Matrix matrixEnd = new Matrix(fillStyleEnd.gradientMatrix);
                                    lineGradientStyle(
                                            fillStyle.fillStyleType,
                                            fillStyle.gradient.gradientRecords,
                                            fillStyleEnd.gradient.gradientRecords,
                                            matrix,
                                            matrixEnd,
                                            fillStyle.gradient.spreadMode,
                                            fillStyle.gradient.interpolationMode,
                                            (fillStyle.gradient instanceof FOCALGRADIENT) ? ((FOCALGRADIENT) fillStyle.gradient).focalPoint : 0,
                                            (fillStyleEnd.gradient instanceof FOCALGRADIENT) ? ((FOCALGRADIENT) fillStyleEnd.gradient).focalPoint : 0
                                    );
                                    break;
                            }
                        }
                    } else {
                        // We should never get here
                        lineStyle(1, 1, new RGB(Color.black), new RGB(Color.BLACK), false, "NORMAL", 0, 0, 0, 3);
                    }
                }
                if (posX != e.getFromX() || posY != e.getFromY()) {
                    moveTo(e.getFromX(), e.getFromY(), e.getFromEndX(), e.getFromEndY());
                }
                if (e instanceof CurvedMorphEdge) {
                    CurvedMorphEdge c = (CurvedMorphEdge) e;
                    curveTo(c.getControlX(), c.getControlY(), c.toX, c.toY, c.getControlEndX(), c.getControlEndY(), c.toEndX, c.toEndY);
                } else {
                    lineTo(e.getToX(), e.getToY(), e.getToEndX(), e.getToEndY());
                }
                posX = e.getToX();
                posY = e.getToY();
            }
            endLines();
        }
    }

    protected List<IMorphEdge> createPathFromEdgeMap(Map<Integer, List<IMorphEdge>> edgeMap) {
        List<IMorphEdge> newPath = new ArrayList<>();
        List<Integer> styleIdxArray = new ArrayList<>();
        for (Integer styleIdx : edgeMap.keySet()) {
            styleIdxArray.add(styleIdx);
        }
        Collections.sort(styleIdxArray);
        for (int i = 0; i < styleIdxArray.size(); i++) {
            appendEdges(newPath, edgeMap.get(styleIdxArray.get(i)));
        }
        return newPath;
    }

    protected void cleanEdgeMap(Map<Integer, List<IMorphEdge>> edgeMap) {
        for (Integer styleIdx : edgeMap.keySet()) {
            List<IMorphEdge> subPath = edgeMap.get(styleIdx);
            if (subPath != null && subPath.size() > 0) {
                int idx;
                IMorphEdge prevEdge = null;
                List<IMorphEdge> tmpPath = new ArrayList<>();
                Map<Long, List<IMorphEdge>> coordMap = createCoordMap(subPath);
                while (subPath.size() > 0) {
                    idx = 0;
                    while (idx < subPath.size()) {
                        if (prevEdge != null) {
                            IMorphEdge subPathEdge = subPath.get(idx);
                            if (prevEdge.getToX() != subPathEdge.getFromX() || prevEdge.getToY() != subPathEdge.getFromY()) {
                                IMorphEdge edge = findNextEdgeInCoordMap(coordMap, prevEdge);
                                if (edge != null) {
                                    idx = subPath.indexOf(edge);
                                } else {
                                    idx = 0;
                                    prevEdge = null;
                                }
                                continue;
                            }
                        }

                        IMorphEdge edge = subPath.remove(idx);
                        tmpPath.add(edge);
                        removeEdgeFromCoordMap(coordMap, edge);
                        prevEdge = edge;
                    }
                }
                edgeMap.put(styleIdx, tmpPath);
            }
        }
    }

    protected Map<Long, List<IMorphEdge>> createCoordMap(List<IMorphEdge> path) {
        Map<Long, List<IMorphEdge>> coordMap = new HashMap<>();
        for (int i = 0; i < path.size(); i++) {
            IMorphEdge edge = path.get(i);
            long fromLong = (((long) edge.getFromX()) << 32) | (edge.getFromY() & 0xffffffffL);
            List<IMorphEdge> coordMapArray = coordMap.get(fromLong);
            if (coordMapArray == null) {
                List<IMorphEdge> list = new ArrayList<>();
                list.add(path.get(i));
                coordMap.put(fromLong, list);
            } else {
                coordMapArray.add(path.get(i));
            }
        }
        return coordMap;
    }

    protected void removeEdgeFromCoordMap(Map<Long, List<IMorphEdge>> coordMap, IMorphEdge edge) {
        long fromLong = (((long) edge.getFromX()) << 32) | (edge.getFromY() & 0xffffffffL);
        List<IMorphEdge> coordMapArray = coordMap.get(fromLong);
        if (coordMapArray != null) {
            if (coordMapArray.size() == 1) {
                coordMap.remove(fromLong);
            } else {
                int i = coordMapArray.indexOf(edge);
                if (i > -1) {
                    coordMapArray.remove(i);
                }
            }
        }
    }

    protected IMorphEdge findNextEdgeInCoordMap(Map<Long, List<IMorphEdge>> coordMap, IMorphEdge edge) {
        long toLong = (((long) edge.getToX()) << 32) | (edge.getToY() & 0xffffffffL);
        List<IMorphEdge> coordMapArray = coordMap.get(toLong);
        if (coordMapArray != null && coordMapArray.size() > 0) {
            return coordMapArray.get(0);
        }
        return null;
    }

    protected void appendFillStyles(List<FILLSTYLE> v1, FILLSTYLE[] v2) {
        v1.addAll(Arrays.asList(v2));
    }

    protected void appendLineStyles(List<LINESTYLE> v1, LINESTYLE[] v2) {
        v1.addAll(Arrays.asList(v2));
    }

    protected void appendEdges(List<IMorphEdge> v1, List<IMorphEdge> v2) {
        for (int i = 0; i < v2.size(); i++) {
            v1.add(v2.get(i));
        }
    }
}
