/*
 *  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.shape;

import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.exporters.commonshape.FillStyle;
import com.jpexs.decompiler.flash.exporters.commonshape.LineStyle;
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 com.jpexs.helpers.Cache;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *
 * @author JPEXS, Claus Wahlers
 */
public abstract class ShapeExporterBase implements IShapeExporter {

    private static final boolean USE_REVERSE_LOOKUP = true;
    
    protected final SHAPE shape;

    private final List<FillStyle> _fillStyles;

    private final List<LineStyle> _lineStyles;

    private final List<List<IEdge>> _fillPaths;

    private final List<List<IEdge>> _linePaths;

    private final ColorTransform colorTransform;

    public ShapeExporterBase(SWF swf, SHAPE shape, ColorTransform colorTransform) {
        this.shape = shape;
        this.colorTransform = colorTransform;

        Cache<SHAPE, ShapeExportData> cache = swf.getShapeExportDataCache();
        ShapeExportData cachedData = cache.get(shape);
        if (cachedData == null) {
            List<FillStyle> fillStyles = new ArrayList<>();
            List<LineStyle> lineStyles = new ArrayList<>();
            if (shape instanceof SHAPEWITHSTYLE) {
                SHAPEWITHSTYLE shapeWithStyle = (SHAPEWITHSTYLE) shape;
                for (FILLSTYLE fillStyle : shapeWithStyle.fillStyles.fillStyles) {
                    fillStyles.add(new FillStyle(fillStyle));
                }

                for (LINESTYLE lineStyle : shapeWithStyle.lineStyles.lineStyles) {
                    lineStyles.add(new LineStyle(lineStyle));
                }
            }

            // Create edge maps
            List<Map<Integer, List<IEdge>>> fillEdgeMaps = new ArrayList<>();
            List<Map<Integer, List<IEdge>>> lineEdgeMaps = new ArrayList<>();
            try {
                createEdgeMaps(shape, fillStyles, lineStyles, fillEdgeMaps, lineEdgeMaps);
            } catch (Throwable t) {
                t.printStackTrace();
            }
            int count = lineEdgeMaps.size();
            List<List<IEdge>> fillPaths = new ArrayList<>(count);
            List<List<IEdge>> linePaths = new ArrayList<>(count);
            for (int i = 0; i < count; i++) {
                fillPaths.add(createPathFromEdgeMap(fillEdgeMaps.get(i)));
                linePaths.add(createPathFromEdgeMap(lineEdgeMaps.get(i)));
            }

            cachedData = new ShapeExportData();
            cachedData.fillPaths = fillPaths;
            cachedData.linePaths = linePaths;
            cachedData.fillStyles = fillStyles;
            cachedData.lineStyles = lineStyles;
            cache.put(shape, cachedData);
        }

        _fillStyles = cachedData.fillStyles;
        _lineStyles = cachedData.lineStyles;
        _fillPaths = cachedData.fillPaths;
        _linePaths = cachedData.linePaths;
    }

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

    private void createEdgeMaps(SHAPE shape, List<FillStyle> fillStyles, List<LineStyle> lineStyles,
            List<Map<Integer, List<IEdge>>> fillEdgeMaps, List<Map<Integer, List<IEdge>>> lineEdgeMaps) {
        int xPos = 0;
        int yPos = 0;
        int fillStyleIdxOffset = 0;
        int lineStyleIdxOffset = 0;
        int currentFillStyleIdx0 = 0;
        int currentFillStyleIdx1 = 0;
        int currentLineStyleIdx = 0;
        List<IEdge> subPath = new ArrayList<>();
        Map<Integer, List<IEdge>> currentFillEdgeMap = new HashMap<>();
        Map<Integer, List<IEdge>> currentLineEdgeMap = new HashMap<>();
        List<SHAPERECORD> records = shape.shapeRecords;
        for (int i = 0; i < records.size(); i++) {
            SHAPERECORD shapeRecord = records.get(i);
            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);
                }
                // 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) {
                    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;
                }
            } 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;
                }
                subPath.add(new StraightEdge(xPosFrom, yPosFrom, xPos, yPos, 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;
                subPath.add(new CurvedEdge(xPosFrom, yPosFrom, xPosControl, yPosControl, xPos, yPos, currentLineStyleIdx, currentFillStyleIdx1));
            } else if (shapeRecord instanceof EndShapeRecord) {
                // We're done. Process the last subpath, if any
                processSubPath(subPath, currentLineStyleIdx, currentFillStyleIdx0, currentFillStyleIdx1, currentFillEdgeMap, currentLineEdgeMap);
                cleanEdgeMap(currentFillEdgeMap);
                cleanEdgeMap(currentLineEdgeMap);
                fillEdgeMaps.add(currentFillEdgeMap);
                lineEdgeMaps.add(currentLineEdgeMap);
            }
        }
    }

    private void processSubPath(List<IEdge> subPath, int lineStyleIdx, int fillStyleIdx0, int fillStyleIdx1,
            Map<Integer, List<IEdge>> currentFillEdgeMap, Map<Integer, List<IEdge>> currentLineEdgeMap) {
        List<IEdge> 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--) {
                IEdge rev = subPath.get(j).reverseWithNewFillStyle(fillStyleIdx0);
                //System.err.println("appending reversed " + rev);
                path.add(rev);
            }
        }
        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);
        }
    }

    private void exportFillPath(List<IEdge> path) {
        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++) {
                IEdge 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()) {
                        FillStyle fillStyle = _fillStyles.get(fillStyleIdx - 1);
                        switch (fillStyle.fillStyleType) {
                            case FILLSTYLE.SOLID:
                                // Solid fill
                                beginFill(colorTransform == null ? fillStyle.color : colorTransform.apply(fillStyle.color));
                                break;
                            case FILLSTYLE.LINEAR_GRADIENT:
                            case FILLSTYLE.RADIAL_GRADIENT:
                            case FILLSTYLE.FOCAL_RADIAL_GRADIENT:
                                // Gradient fill
                                beginGradientFill(
                                        fillStyle.fillStyleType,
                                        colorTransform == null ? fillStyle.gradient.gradientRecords : colorTransform.apply(fillStyle.gradient.gradientRecords),
                                        fillStyle.gradientMatrix,
                                        fillStyle.gradient.spreadMode,
                                        fillStyle.gradient.interpolationMode,
                                        (fillStyle.gradient instanceof FOCALGRADIENT) ? ((FOCALGRADIENT) fillStyle.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
                                beginBitmapFill(
                                        fillStyle.bitmapId,
                                        fillStyle.bitmapMatrix,
                                        (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);
                    }
                }
                if (posX != e.getFromX() || posY != e.getFromY()) {
                    moveTo(e.getFromX(), e.getFromY());
                }
                if (e instanceof CurvedEdge) {
                    CurvedEdge c = (CurvedEdge) e;
                    curveTo(c.getControlX(), c.getControlY(), c.toX, c.toY);
                } else {
                    lineTo(e.getToX(), e.getToY());
                }
                posX = e.getToX();
                posY = e.getToY();
            }
            if (fillStyleIdx != Integer.MAX_VALUE) {
                endFill();
            }
            endFills();
        }
    }

    private void exportLinePath(List<IEdge> path) {
        int posX = Integer.MAX_VALUE;
        int posY = Integer.MAX_VALUE;
        int lineStyleIdx = Integer.MAX_VALUE;
        if (path.size() > 0) {
            boolean autoClose = true;
            beginLines();
            for (int i = 0; i < path.size(); i++) {
                IEdge e = path.get(i);
                if (lineStyleIdx != e.getLineStyleIdx()) {
                    lineStyleIdx = e.getLineStyleIdx();
                    posX = Integer.MAX_VALUE;
                    posY = Integer.MAX_VALUE;
                    LineStyle lineStyle = null;
                    try {
                        lineStyle = _lineStyles.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;
                        autoClose = true;
                        if (lineStyle.isLineStyle2) {
                            if (lineStyle.noClose) {
                                autoClose = false;
                            }
                            if (lineStyle.noHScaleFlag && lineStyle.noVScaleFlag) {
                                scaleMode = "NONE";
                            } else if (lineStyle.noHScaleFlag) {
                                scaleMode = "VERTICAL";
                            } else if (lineStyle.noVScaleFlag) {
                                scaleMode = "HORIZONTAL";
                            }
                            pixelHintingFlag = lineStyle.pixelHintingFlag;
                            startCapStyle = lineStyle.startCapStyle;
                            endCapStyle = lineStyle.endCapStyle;
                            joinStyle = lineStyle.joinStyle;
                            miterLimitFactor = lineStyle.miterLimitFactor;
                            hasFillFlag = lineStyle.hasFillFlag;
                        }
                        lineStyle(
                                lineStyle.width,
                                colorTransform == null ? lineStyle.color : colorTransform.apply(lineStyle.color),
                                pixelHintingFlag,
                                scaleMode,
                                startCapStyle,
                                endCapStyle,
                                joinStyle,
                                miterLimitFactor);

                        if (hasFillFlag) {
                            FillStyle fillStyle = lineStyle.fillType;
                            switch (fillStyle.fillStyleType) {
                                case FILLSTYLE.LINEAR_GRADIENT:
                                case FILLSTYLE.RADIAL_GRADIENT:
                                case FILLSTYLE.FOCAL_RADIAL_GRADIENT:
                                    // Gradient fill
                                    lineGradientStyle(
                                            fillStyle.fillStyleType,
                                            fillStyle.gradient.gradientRecords,
                                            fillStyle.gradientMatrix,
                                            fillStyle.gradient.spreadMode,
                                            fillStyle.gradient.interpolationMode,
                                            (fillStyle.gradient instanceof FOCALGRADIENT) ? ((FOCALGRADIENT) fillStyle.gradient).focalPoint : 0
                                    );
                                    break;
                                case FILLSTYLE.CLIPPED_BITMAP:
                                case FILLSTYLE.REPEATING_BITMAP:
                                case FILLSTYLE.NON_SMOOTHED_CLIPPED_BITMAP:
                                case FILLSTYLE.NON_SMOOTHED_REPEATING_BITMAP:
                                    lineBitmapStyle(fillStyle.bitmapId,
                                            fillStyle.bitmapMatrix,
                                            (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 {
                        // We should never get here
                        lineStyle(1, new RGB(Color.black), false, "NORMAL", 0, 0, 0, 3);
                    }
                }
                if (posX != e.getFromX() || posY != e.getFromY()) {
                    moveTo(e.getFromX(), e.getFromY());
                }
                if (e instanceof CurvedEdge) {
                    CurvedEdge c = (CurvedEdge) e;
                    curveTo(c.getControlX(), c.getControlY(), c.toX, c.toY);
                } else {
                    lineTo(e.getToX(), e.getToY());
                }
                posX = e.getToX();
                posY = e.getToY();
            }
            IEdge firstEdge = path.get(0);
            endLines(autoClose && firstEdge.getFromX() == posX && firstEdge.getFromY() == posY);
        }
    }

    private List<IEdge> createPathFromEdgeMap(Map<Integer, List<IEdge>> edgeMap) {
        List<IEdge> 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;
    }

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

                                    if (revEdge != null) {
                                        if (USE_REVERSE_LOOKUP) {
                                            idx = subPath.indexOf(revEdge);
                                            IEdge r = revEdge.reverseWithNewFillStyle(revEdge.getFillStyleIdx());
                                            updateEdgeInCoordMap(coordMap, revEdge, r);
                                            updateEdgeInReverseCoordMap(reverseCoordMap, revEdge, r);
                                            subPath.set(idx, r);
                                        } else {
                                            idx = 0;
                                            prevEdge = null;
                                        }
                                    } else {
                                        idx = 0;
                                        prevEdge = null;
                                    }
                                }
                                continue;
                            }
                        }

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

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

    private Map<Long, List<IEdge>> createReverseCoordMap(List<IEdge> path) {
        Map<Long, List<IEdge>> coordMap = new HashMap<>();
        for (int i = 0; i < path.size(); i++) {
            IEdge edge = path.get(i);
            long toLong = (((long) edge.getToX()) << 32) | (edge.getToY() & 0xffffffffL);
            List<IEdge> coordMapArray = coordMap.get(toLong);
            if (coordMapArray == null) {
                List<IEdge> list = new ArrayList<>();
                list.add(path.get(i));
                coordMap.put(toLong, list);
            } else {
                coordMapArray.add(path.get(i));
            }
        }
        return coordMap;
    }

    private void removeEdgeFromCoordMap(Map<Long, List<IEdge>> coordMap, IEdge edge) {
        long fromLong = (((long) edge.getFromX()) << 32) | (edge.getFromY() & 0xffffffffL);
        List<IEdge> 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);
                }
            }
        }
    }

    private void removeEdgeFromReverseCoordMap(Map<Long, List<IEdge>> coordMap, IEdge edge) {
        long toLong = (((long) edge.getToX()) << 32) | (edge.getToY() & 0xffffffffL);
        List<IEdge> coordMapArray = coordMap.get(toLong);
        if (coordMapArray != null) {
            if (coordMapArray.size() == 1) {
                coordMap.remove(toLong);
            } else {
                int i = coordMapArray.indexOf(edge);
                if (i > -1) {
                    coordMapArray.remove(i);
                }
            }
        }
    }

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

    private IEdge updateEdgeInCoordMap(Map<Long, List<IEdge>> coordMap, IEdge edge, IEdge newEdge) {
        long fromLong = (((long) edge.getFromX()) << 32) | (edge.getFromY() & 0xffffffffL);

        coordMap.get(fromLong).remove(edge);

        long fromLong2 = (((long) newEdge.getFromX()) << 32) | (newEdge.getFromY() & 0xffffffffL);

        if (!coordMap.containsKey(fromLong2)) {
            coordMap.put(fromLong2, new ArrayList<>());
        }
        coordMap.get(fromLong2).add(newEdge);
        return null;
    }

    private IEdge updateEdgeInReverseCoordMap(Map<Long, List<IEdge>> coordMap, IEdge edge, IEdge newEdge) {
        long toLong = (((long) edge.getToX()) << 32) | (edge.getToY() & 0xffffffffL);

        coordMap.get(toLong).remove(edge);

        long toLong2 = (((long) newEdge.getToX()) << 32) | (newEdge.getToY() & 0xffffffffL);

        if (!coordMap.containsKey(toLong2)) {
            coordMap.put(toLong2, new ArrayList<>());
        }
        coordMap.get(toLong2).add(newEdge);
        return null;
    }

    private void appendFillStyles(List<FillStyle> v1, FILLSTYLE[] v2) {
        for (FILLSTYLE s : v2) {
            v1.add(new FillStyle(s));
        }
    }

    private void appendLineStyles(List<LineStyle> v1, LINESTYLE[] v2) {
        for (LINESTYLE s : v2) {
            v1.add(new LineStyle(s));
        }
    }

    private void appendEdges(List<IEdge> v1, List<IEdge> v2) {
        for (int i = 0; i < v2.size(); i++) {
            //System.err.println("appending " + v2.get(i));
            v1.add(v2.get(i));
        }
    }
}
