/*
 * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.javafx.geom;

final class Order1 extends Curve {
    private double x0;
    private double y0;
    private double x1;
    private double y1;
    private double xmin;
    private double xmax;

    public Order1(double x0, double y0,
                  double x1, double y1,
                  int direction)
    {
        super(direction);
        this.x0 = x0;
        this.y0 = y0;
        this.x1 = x1;
        this.y1 = y1;
        if (x0 < x1) {
            this.xmin = x0;
            this.xmax = x1;
        } else {
            this.xmin = x1;
            this.xmax = x0;
        }
    }

    public int getOrder() {
        return 1;
    }

    public double getXTop() {
        return x0;
    }

    public double getYTop() {
        return y0;
    }

    public double getXBot() {
        return x1;
    }

    public double getYBot() {
        return y1;
    }

    public double getXMin() {
        return xmin;
    }

    public double getXMax() {
        return xmax;
    }

    public double getX0() {
        return (direction == INCREASING) ? x0 : x1;
    }

    public double getY0() {
        return (direction == INCREASING) ? y0 : y1;
    }

    public double getX1() {
        return (direction == DECREASING) ? x0 : x1;
    }

    public double getY1() {
        return (direction == DECREASING) ? y0 : y1;
    }

    public double XforY(double y) {
        if (x0 == x1 || y <= y0) {
            return x0;
        }
        if (y >= y1) {
            return x1;
        }
        // assert(y0 != y1); /* No horizontal lines... */
        return (x0 + (y - y0) * (x1 - x0) / (y1 - y0));
    }

    public double TforY(double y) {
        if (y <= y0) {
            return 0;
        }
        if (y >= y1) {
            return 1;
        }
        return (y - y0) / (y1 - y0);
    }

    public double XforT(double t) {
        return x0 + t * (x1 - x0);
    }

    public double YforT(double t) {
        return y0 + t * (y1 - y0);
    }

    public double dXforT(double t, int deriv) {
        switch (deriv) {
        case 0:
            return x0 + t * (x1 - x0);
        case 1:
            return (x1 - x0);
        default:
            return 0;
        }
    }

    public double dYforT(double t, int deriv) {
        switch (deriv) {
        case 0:
            return y0 + t * (y1 - y0);
        case 1:
            return (y1 - y0);
        default:
            return 0;
        }
    }

    public double nextVertical(double t0, double t1) {
        return t1;
    }

    @Override
    public boolean accumulateCrossings(Crossings c) {
        double xlo = c.getXLo();
        double ylo = c.getYLo();
        double xhi = c.getXHi();
        double yhi = c.getYHi();
        if (xmin >= xhi) {
            return false;
        }
        double xstart, ystart, xend, yend;
        if (y0 < ylo) {
            if (y1 <= ylo) {
                return false;
            }
            ystart = ylo;
            xstart = XforY(ylo);
        } else {
            if (y0 >= yhi) {
                return false;
            }
            ystart = y0;
            xstart = x0;
        }
        if (y1 > yhi) {
            yend = yhi;
            xend = XforY(yhi);
        } else {
            yend = y1;
            xend = x1;
        }
        if (xstart >= xhi && xend >= xhi) {
            return false;
        }
        if (xstart > xlo || xend > xlo) {
            return true;
        }
        c.record(ystart, yend, direction);
        return false;
    }

    public void enlarge(RectBounds r) {
        r.add((float) x0, (float) y0);
        r.add((float) x1, (float) y1);
    }

    public Curve getSubCurve(double ystart, double yend, int dir) {
        if (ystart == y0 && yend == y1) {
            return getWithDirection(dir);
        }
        if (x0 == x1) {
            return new Order1(x0, ystart, x1, yend, dir);
        }
        double num = x0 - x1;
        double denom = y0 - y1;
        double xstart = (x0 + (ystart - y0) * num / denom);
        double xend = (x0 + (yend - y0) * num / denom);
        return new Order1(xstart, ystart, xend, yend, dir);
    }

    public Curve getReversedCurve() {
        return new Order1(x0, y0, x1, y1, -direction);
    }

    @Override
    public int compareTo(Curve other, double yrange[]) {
        if (!(other instanceof Order1)) {
            return super.compareTo(other, yrange);
        }
        Order1 c1 = (Order1) other;
        if (yrange[1] <= yrange[0]) {
            throw new InternalError("yrange already screwed up...");
        }
        yrange[1] = Math.min(Math.min(yrange[1], y1), c1.y1);
        if (yrange[1] <= yrange[0]) {
            throw new InternalError("backstepping from "+yrange[0]+" to "+yrange[1]);
        }
        if (xmax <= c1.xmin) {
            return (xmin == c1.xmax) ? 0 : -1;
        }
        if (xmin >= c1.xmax) {
            return 1;
        }
        /*
         * If "this" is curve A and "other" is curve B, then...
         * xA(y) = x0A + (y - y0A) (x1A - x0A) / (y1A - y0A)
         * xB(y) = x0B + (y - y0B) (x1B - x0B) / (y1B - y0B)
         * xA(y) == xB(y)
         * x0A + (y - y0A) (x1A - x0A) / (y1A - y0A)
         *    == x0B + (y - y0B) (x1B - x0B) / (y1B - y0B)
         * 0 == x0A (y1A - y0A) (y1B - y0B) + (y - y0A) (x1A - x0A) (y1B - y0B)
         *    - x0B (y1A - y0A) (y1B - y0B) - (y - y0B) (x1B - x0B) (y1A - y0A)
         * 0 == (x0A - x0B) (y1A - y0A) (y1B - y0B)
         *    + (y - y0A) (x1A - x0A) (y1B - y0B)
         *    - (y - y0B) (x1B - x0B) (y1A - y0A)
         * If (dxA == x1A - x0A), etc...
         * 0 == (x0A - x0B) * dyA * dyB
         *    + (y - y0A) * dxA * dyB
         *    - (y - y0B) * dxB * dyA
         * 0 == (x0A - x0B) * dyA * dyB
         *    + y * dxA * dyB - y0A * dxA * dyB
         *    - y * dxB * dyA + y0B * dxB * dyA
         * 0 == (x0A - x0B) * dyA * dyB
         *    + y * dxA * dyB - y * dxB * dyA
         *    - y0A * dxA * dyB + y0B * dxB * dyA
         * 0 == (x0A - x0B) * dyA * dyB
         *    + y * (dxA * dyB - dxB * dyA)
         *    - y0A * dxA * dyB + y0B * dxB * dyA
         * y == ((x0A - x0B) * dyA * dyB
         *       - y0A * dxA * dyB + y0B * dxB * dyA)
         *    / (-(dxA * dyB - dxB * dyA))
         * y == ((x0A - x0B) * dyA * dyB
         *       - y0A * dxA * dyB + y0B * dxB * dyA)
         *    / (dxB * dyA - dxA * dyB)
         */
        double dxa = x1 - x0;
        double dya = y1 - y0;
        double dxb = c1.x1 - c1.x0;
        double dyb = c1.y1 - c1.y0;
        double denom = dxb * dya - dxa * dyb;
        double y;
        if (denom != 0) {
            double num = ((x0 - c1.x0) * dya * dyb
                          - y0 * dxa * dyb
                          + c1.y0 * dxb * dya);
            y = num / denom;
            if (y <= yrange[0]) {
                // intersection is above us
                // Use bottom-most common y for comparison
                y = Math.min(y1, c1.y1);
            } else {
                // intersection is below the top of our range
                if (y < yrange[1]) {
                    // If intersection is in our range, adjust valid range
                    yrange[1] = y;
                }
                // Use top-most common y for comparison
                y = Math.max(y0, c1.y0);
            }
        } else {
            // lines are parallel, choose any common y for comparison
            // Note - prefer an endpoint for speed of calculating the X
            // (see shortcuts in Order1.XforY())
            y = Math.max(y0, c1.y0);
        }
        return orderof(XforY(y), c1.XforY(y));
    }

    public int getSegment(float coords[]) {
        if (direction == INCREASING) {
            coords[0] = (float) x1;
            coords[1] = (float) y1;
        } else {
            coords[0] = (float) x0;
            coords[1] = (float) y0;
        }
        return PathIterator.SEG_LINETO;
    }
}
