/*
 *
 * This file is part of the iText (R) project.
 * Copyright (c) 2014-2015 iText Group NV
 * Authors: Bruno Lowagie, Paulo Soares, et al.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation with the addition of the
 * following permission added to Section 15 as permitted in Section 7(a):
 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
 * ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
 * OF THIRD PARTY RIGHTS
 *
 * This program 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 Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
 * http://itextpdf.com/terms-of-use/
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License,
 * a covered work must retain the producer line in every PDF that is created
 * or manipulated using iText.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial activities involving the iText software without
 * disclosing the source code of your own applications.
 * These activities include: offering paid services to customers as an ASP,
 * serving PDFs on the fly in a web application, shipping iText with a closed
 * source product.
 *
 * For more information, please contact iText Software Corp. at this
 * address: sales@itextpdf.com
 *
 *
 * This class is based on the C# open source freeware library Clipper:
 * http://www.angusj.com/delphi/clipper.php
 * The original classes were distributed under the Boost Software License:
 *
 * Freeware for both open source and commercial applications
 * Copyright 2010-2014 Angus Johnson
 * Boost Software License - Version 1.0 - August 17th, 2003
 *
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 *
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
package com.itextpdf.text.pdf.parser.clipper;

import java.math.BigInteger;
import java.util.logging.Logger;

import com.itextpdf.text.pdf.parser.clipper.Clipper.ClipType;
import com.itextpdf.text.pdf.parser.clipper.Clipper.Direction;
import com.itextpdf.text.pdf.parser.clipper.Clipper.PolyFillType;
import com.itextpdf.text.pdf.parser.clipper.Clipper.PolyType;
import com.itextpdf.text.pdf.parser.clipper.Point.LongPoint;

class Edge {
    static enum Side {
        LEFT, RIGHT
    }

    static boolean doesE2InsertBeforeE1( Edge e1, Edge e2 ) {
        if (e2.current.getX() == e1.current.getX()) {
            if (e2.top.getY() > e1.top.getY()) {
                return e2.top.getX() < topX( e1, e2.top.getY() );
            }
            else {
                return e1.top.getX() > topX( e2, e1.top.getY() );
            }
        }
        else {
            return e2.current.getX() < e1.current.getX();
        }
    }

    static boolean slopesEqual( Edge e1, Edge e2, boolean useFullRange ) {
        if (useFullRange) {
            return BigInteger.valueOf(e1.getDelta().getY()).multiply(BigInteger.valueOf(e2.getDelta().getX())).equals(
                   BigInteger.valueOf(e1.getDelta().getX()).multiply(BigInteger.valueOf(e2.getDelta().getY())));
        } else {
            return (e1.getDelta().getY()) * (e2.getDelta().getX()) == (e1.getDelta().getX()) * (e2.getDelta().getY());
        }
    }

    static void swapPolyIndexes( Edge edge1, Edge edge2 ) {
        final int outIdx = edge1.outIdx;
        edge1.outIdx = edge2.outIdx;
        edge2.outIdx = outIdx;
    }

    static void swapSides( Edge edge1, Edge edge2 ) {
        final Edge.Side side = edge1.side;
        edge1.side = edge2.side;
        edge2.side = side;
    }

    static long topX( Edge edge, long currentY ) {
        if (currentY == edge.getTop().getY()) {
            return edge.getTop().getX();
        }
        return (edge.getBot().getX() + Math.round( edge.deltaX * (currentY - edge.getBot().getY()) ));
    }

    private final LongPoint bot;

    private final LongPoint current;

    private final LongPoint top;

    private final LongPoint delta;
    double deltaX;

    PolyType polyTyp;

    Edge.Side side;

    int windDelta; //1 or -1 depending on winding direction

    int windCnt;
    int windCnt2; //winding count of the opposite polytype
    int outIdx;
    Edge next;
    Edge prev;
    Edge nextInLML;
    Edge nextInAEL;
    Edge prevInAEL;
    Edge nextInSEL;
    Edge prevInSEL;

    protected final static int SKIP = -2;

    protected final static int UNASSIGNED = -1;

    protected final static double HORIZONTAL = -3.4E+38;

    private final static Logger LOGGER = Logger.getLogger( Edge.class.getName() );

    public Edge() {
        delta = new LongPoint();
        top = new LongPoint();
        bot = new LongPoint();
        current = new LongPoint();
    }

    public Edge findNextLocMin() {
        Edge e = this;
        Edge e2;
        for (;;) {
            while (!e.bot.equals( e.prev.bot ) || e.current.equals( e.top )) {
                e = e.next;
            }
            if (e.deltaX != Edge.HORIZONTAL && e.prev.deltaX != Edge.HORIZONTAL) {
                break;
            }
            while (e.prev.deltaX == Edge.HORIZONTAL) {
                e = e.prev;
            }
            e2 = e;
            while (e.deltaX == Edge.HORIZONTAL) {
                e = e.next;
            }
            if (e.top.getY() == e.prev.bot.getY()) {
                continue; //ie just an intermediate horz.
            }
            if (e2.prev.bot.getX() < e.bot.getX()) {
                e = e2;
            }
            break;
        }
        return e;
    }

    public LongPoint getBot() {
        return bot;
    }

    public LongPoint getCurrent() {
        return current;
    }

    public LongPoint getDelta() {
        return delta;
    }

    public Edge getMaximaPair() {
        Edge result = null;
        if (next.top.equals( top ) && next.nextInLML == null) {
            result = next;
        }
        else if (prev.top.equals( top ) && prev.nextInLML == null) {
            result = prev;
        }
        if (result != null && (result.outIdx == Edge.SKIP || result.nextInAEL == result.prevInAEL && !result.isHorizontal())) {
            return null;
        }
        return result;
    }

    public Edge getNextInAEL( Direction direction ) {
        return direction == Direction.LEFT_TO_RIGHT ? nextInAEL : prevInAEL;
    }

    public LongPoint getTop() {
        return top;
    }

    public boolean isContributing( PolyFillType clipFillType, PolyFillType subjFillType, ClipType clipType ) {
        LOGGER.entering( Edge.class.getName(), "isContributing" );

        PolyFillType pft, pft2;
        if (polyTyp == PolyType.SUBJECT) {
            pft = subjFillType;
            pft2 = clipFillType;
        }
        else {
            pft = clipFillType;
            pft2 = subjFillType;
        }

        switch (pft) {
            case EVEN_ODD:
                //return false if a subj line has been flagged as inside a subj polygon
                if (windDelta == 0 && windCnt != 1) {
                    return false;
                }
                break;
            case NON_ZERO:
                if (Math.abs( windCnt ) != 1) {
                    return false;
                }
                break;
            case POSITIVE:
                if (windCnt != 1) {
                    return false;
                }
                break;
            default: //PolyFillType.pftNegative
                if (windCnt != -1) {
                    return false;
                }
                break;
        }

        switch (clipType) {
            case INTERSECTION:
                switch (pft2) {
                    case EVEN_ODD:
                    case NON_ZERO:
                        return windCnt2 != 0;
                    case POSITIVE:
                        return windCnt2 > 0;
                    default:
                        return windCnt2 < 0;
                }
            case UNION:
                switch (pft2) {
                    case EVEN_ODD:
                    case NON_ZERO:
                        return windCnt2 == 0;
                    case POSITIVE:
                        return windCnt2 <= 0;
                    default:
                        return windCnt2 >= 0;
                }
            case DIFFERENCE:
                if (polyTyp == PolyType.SUBJECT) {
                    switch (pft2) {
                        case EVEN_ODD:
                        case NON_ZERO:
                            return windCnt2 == 0;
                        case POSITIVE:
                            return windCnt2 <= 0;
                        default:
                            return windCnt2 >= 0;
                    }
                }
                else {
                    switch (pft2) {
                        case EVEN_ODD:
                        case NON_ZERO:
                            return windCnt2 != 0;
                        case POSITIVE:
                            return windCnt2 > 0;
                        default:
                            return windCnt2 < 0;
                    }
                }
            case XOR:
                if (windDelta == 0) {
                    switch (pft2) {
                        case EVEN_ODD:
                        case NON_ZERO:
                            return windCnt2 == 0;
                        case POSITIVE:
                            return windCnt2 <= 0;
                        default:
                            return windCnt2 >= 0;
                    }
                }
                else {
                    return true;
                }
        }
        return true;
    }

    public boolean isEvenOddAltFillType( PolyFillType clipFillType, PolyFillType subjFillType ) {
        if (polyTyp == PolyType.SUBJECT) {
            return clipFillType == PolyFillType.EVEN_ODD;
        }
        else {
            return subjFillType == PolyFillType.EVEN_ODD;
        }
    }

    public boolean isEvenOddFillType( PolyFillType clipFillType, PolyFillType subjFillType ) {
        if (polyTyp == PolyType.SUBJECT) {
            return subjFillType == PolyFillType.EVEN_ODD;
        }
        else {
            return clipFillType == PolyFillType.EVEN_ODD;
        }
    }

    public boolean isHorizontal() {
        return delta.getY() == 0;
    }

    public boolean isIntermediate( double y ) {
        return top.getY() == y && nextInLML != null;
    }

    public boolean isMaxima( double Y ) {
        return top.getY() == Y && nextInLML == null;
    }

    public void reverseHorizontal() {
        //swap horizontal edges' top and bottom x's so they follow the natural
        //progression of the bounds - ie so their xbots will align with the
        //adjoining lower edge. [Helpful in the ProcessHorizontal() method.]
        long temp = top.getX();
        top.setX( bot.getX() );
        bot.setX( temp );

        temp = top.getZ();
        top.setZ( bot.getZ() );
        bot.setZ( temp );

    }

    public void setBot( LongPoint bot ) {
        this.bot.set( bot );
    }

    public void setCurrent( LongPoint current ) {
        this.current.set( current );
    }

    public void setTop( LongPoint top ) {
        this.top.set( top );
    }

    @Override
    public String toString() {
        return "TEdge [Bot=" + bot + ", Curr=" + current + ", Top=" + top + ", Delta=" + delta + ", Dx=" + deltaX + ", PolyTyp=" + polyTyp + ", Side=" + side
                        + ", WindDelta=" + windDelta + ", WindCnt=" + windCnt + ", WindCnt2=" + windCnt2 + ", OutIdx=" + outIdx + ", Next=" + next + ", Prev="
                        + prev + ", NextInLML=" + nextInLML + ", NextInAEL=" + nextInAEL + ", PrevInAEL=" + prevInAEL + ", NextInSEL=" + nextInSEL
                        + ", PrevInSEL=" + prevInSEL + "]";
    }

    public void updateDeltaX() {

        delta.setX( top.getX() - bot.getX() );
        delta.setY( top.getY() - bot.getY() );
        if (delta.getY() == 0) {
            deltaX = Edge.HORIZONTAL;
        }
        else {
            deltaX = (double) delta.getX() / delta.getY();
        }
    }

};
