package com.srbenoit.render;

import com.srbenoit.log.LoggedObject;

/**
 * A two-dimensional line segment.
 */
public class LineSegment2D extends LoggedObject {

    /** out-code indicating a point has Y coordinate above the maximum */
    private static final int MAXY = 0x01;

    /** out-code indicating a point has Y coordinate below the minimum */
    private static final int MINY = 0x02;

    /** out-code indicating a point has X coordinate above the maximum */
    private static final int MAXX = 0x04;

    /** out-code indicating a point has X coordinate below the minimum */
    private static final int MINX = 0x08;

    /** the X coordinate of the first end */
    private double xCoord1;

    /** the Y coordinate of the first end */
    private double yCoord1;

    /** the X coordinate of the second end */
    private double xCoord2;

    /** the Y coordinate of the second end */
    private double yCoord2;

    /**
     * Constructs a new <code>LineSegment2D</code>.
     *
     * @param  end1x  the X coordinate of the first end
     * @param  end1y  the Y coordinate of the first end
     * @param  end2x  the X coordinate of the second end
     * @param  end2y  the Y coordinate of the second end
     */
    public LineSegment2D(final double end1x, final double end1y, final double end2x,
        final double end2y) {

        this.xCoord1 = end1x;
        this.yCoord1 = end1y;
        this.xCoord2 = end2x;
        this.yCoord2 = end2y;
    }

    /**
     * Gets the X coordinate of the first end of the line.
     *
     * @return  the X coordinate
     */
    public double getX1() {

        return this.xCoord1;
    }

    /**
     * Gets the Y coordinate of the first end of the line.
     *
     * @return  the Y coordinate
     */
    public double getY1() {

        return this.yCoord1;
    }

    /**
     * Gets the X coordinate of the second end of the line.
     *
     * @return  the X coordinate
     */
    public double getX2() {

        return this.xCoord2;
    }

    /**
     * Gets the Y coordinate of the second end of the line.
     *
     * @return  the Y coordinate
     */
    public double getY2() {

        return this.yCoord2;
    }

    /**
     * An implementation of the Cohen-Sutherland algorithm for line clipping.
     *
     * @param   minX  the lower limit on X
     * @param   maxX  the upper limit on X
     * @param   minY  the lower limit on Y
     * @param   maxY  the upper limit on Y
     * @return  <code>true</code> if part of the line fell within the clip window and the line was
     *          clipped; <code>false</code> if the line falls completely outside the window and can
     *          be culled
     */
    public boolean clip(final double minX, final double maxX, final double minY,
        final double maxY) {

        int out1;
        int out2;
        double recSlope;
        double slope;
        boolean visible;

        out1 = outcode1(minX, maxX, minY, maxY);
        out2 = outcode2(minX, maxX, minY, maxY);

        // Clip in a loop until both outcodes are zero
        for (;;) {

            if ((out1 & out2) == 0) {
                visible = true;

                // Not a trivial acceptance
                if ((out1 & MAXY) == MAXY) {
                    recSlope = (this.xCoord2 - this.xCoord1) / (this.yCoord2 - this.yCoord1);
                    this.xCoord1 += recSlope * (maxY - this.yCoord1);
                    this.yCoord1 = maxY;
                    out1 = outcode1(minX, maxX, minY, maxY);
                } else if ((out1 & MINY) == MINY) {
                    recSlope = (this.xCoord2 - this.xCoord1) / (this.yCoord2 - this.yCoord1);
                    this.xCoord1 += recSlope * (minY - this.yCoord1);
                    this.yCoord1 = minY;
                    out1 = outcode1(minX, maxX, minY, maxY);
                } else if ((out1 & MAXX) == MAXX) {
                    slope = (this.yCoord2 - this.yCoord1) / (this.xCoord2 - this.xCoord1);
                    this.yCoord1 += slope * (maxX - this.xCoord1);
                    this.xCoord1 = maxX;
                    out1 = outcode1(minX, maxX, minY, maxY);
                } else if ((out1 & MINX) == MINX) {
                    slope = (this.yCoord2 - this.yCoord1) / (this.xCoord2 - this.xCoord1);
                    this.yCoord1 += slope * (minX - this.xCoord1);
                    this.xCoord1 = minX;
                    out1 = outcode1(minX, maxX, minY, maxY);
                } else if ((out2 & MAXY) == MAXY) {
                    recSlope = (this.xCoord1 - this.xCoord2) / (this.yCoord1 - this.yCoord2);
                    this.xCoord2 += recSlope * (maxY - this.yCoord2);
                    this.yCoord2 = maxY;
                    out2 = outcode2(minX, maxX, minY, maxY);
                } else if ((out2 & MINY) == MINY) {
                    recSlope = (this.xCoord1 - this.xCoord2) / (this.yCoord1 - this.yCoord2);
                    this.xCoord2 += recSlope * (minY - this.yCoord2);
                    this.yCoord2 = minY;
                    out2 = outcode2(minX, maxX, minY, maxY);
                } else if ((out2 & MAXX) == MAXX) {
                    slope = (this.yCoord1 - this.yCoord2) / (this.xCoord1 - this.xCoord2);
                    this.yCoord2 += slope * (maxX - this.xCoord2);
                    this.xCoord2 = maxX;
                    out2 = outcode2(minX, maxX, minY, maxY);
                } else if ((out2 & MINX) == MINX) {
                    slope = (this.yCoord1 - this.yCoord2) / (this.xCoord1 - this.xCoord2);
                    this.yCoord2 += slope * (minX - this.xCoord2);
                    this.xCoord2 = minX;
                    out2 = outcode2(minX, maxX, minY, maxY);
                } else {
                    break;
                }
            } else {
                visible = false;

                break;
            }
        }

        return visible;
    }

    /**
     * Compute the out-code for the Cohen-Sutherland line clipping algorithm.
     *
     * @param   minX  the lower limit on X
     * @param   maxX  the upper limit on X
     * @param   minY  the lower limit on Y
     * @param   maxY  the upper limit on Y
     * @return  the out-code
     */
    private int outcode1(final double minX, final double maxX, final double minY,
        final double maxY) {

        int code;

        code = 0;

        if (this.xCoord1 > maxY) {
            code |= MAXY;
        }

        if (this.yCoord1 < minY) {
            code |= MINY;
        }

        if (this.xCoord1 > maxX) {
            code |= MAXX;
        }

        if (this.xCoord1 < minX) {
            code |= MINX;
        }

        return code;
    }

    /**
     * Compute the out-code for the Cohen-Sutherland line clipping algorithm.
     *
     * @param   minX  the lower limit on X
     * @param   maxX  the upper limit on X
     * @param   minY  the lower limit on Y
     * @param   maxY  the upper limit on Y
     * @return  the out-code
     */
    private int outcode2(final double minX, final double maxX, final double minY,
        final double maxY) {

        int code;

        code = 0;

        if (this.yCoord2 > maxY) {
            code |= MAXY;
        }

        if (this.yCoord2 < minY) {
            code |= MINY;
        }

        if (this.xCoord2 > maxX) {
            code |= MAXX;
        }

        if (this.xCoord2 < minX) {
            code |= MINX;
        }

        return code;
    }

    /**
     * Generates the string representation of the line.
     *
     * @return  the string representation
     */
    @Override public String toString() {
        StringBuilder str;

        str = new StringBuilder(50);

        str.append("Line Segment: [(");
        str.append(Float.toString((float) this.xCoord1));
        str.append(',');
        str.append(Float.toString((float) this.yCoord1));
        str.append(")-(");
        str.append(Float.toString((float) this.xCoord2));
        str.append(',');
        str.append(Float.toString((float) this.yCoord2));
        str.append(")]");

        return str.toString();
    }
}
