import Drawable;
import Vertex2D;
import java.awt.Color;

class Triangle implements Drawable {
    
    class Edge
    {
        public Vertex2D v_start, v_end;
        public double dx;  // slope in terms of x as a func of y
        public double da, dr, dg, db; // slope of change in color in terms of y
        int a_start, r_start, g_start, b_start; // color values
        int a_end, r_end, g_end, b_end;        
        
        // constructor hopes that the vertices will not be changed
        // saves memory this way
        public Edge(Vertex2D v0, Vertex2D v1)
        {
            v_start = v0;
            v_end = v1;
            dx = ( v_end.x - v_start.x ) / ( v_end.y - v_start.y );
            

            a_start = getA(v_start.argb);
            r_start = getR(v_start.argb);
            g_start = getG(v_start.argb);
            b_start = getB(v_start.argb);

            a_end = getA(v_end.argb);
            r_end = getR(v_end.argb);
            g_end = getG(v_end.argb);
            b_end = getB(v_end.argb);
            
            da = ( a_end - a_start ) / ( v_end.y - v_start.y );
            dr = ( r_end - r_start ) / ( v_end.y - v_start.y );
            dg = ( g_end - g_start ) / ( v_end.y - v_start.y );
            db = ( b_end - b_start ) / ( v_end.y - v_start.y );            
            
        }
        
        // returns the x value of the edge for a given y value
        public double getX(double y)
        {
            return v_start.x + (y - v_start.y) * dx;
        }
    }

    // get's values from color
    private int getA(int pix)
    {
        return (pix >> 24) & 0xFF;
    }
    
    private int getR(int pix)
    {
        return (pix >> 16) & 0xFF;
    }
    
    private int getG(int pix)
    {
        return (pix >> 8) & 0xFF;
    }
    
    private int getB(int pix)
    {
        return (pix >> 0) & 0xFF;
    }
    
    private int getRGB(int a, int r, int g, int b)
    {
        return (a << 24) | (r << 16) | (g << 8) | (b << 0);
    }
    
    
    protected Vertex2D v[];

    // indices of min/max values of x, y
    int minX, minY;
    int maxX, maxY;
    int midX, midY;
    

    public Triangle() {
    }

    public Triangle(Vertex2D v0, Vertex2D v1, Vertex2D v2) {
        v = new Vertex2D[3];
        v[0] = v0;
        v[1] = v1;
        v[2] = v2;
        
        for (int i = 0; i < 3; i++)
        {
            v[i].x = (int)(v[i].x + .5);
            v[i].y = (int)(v[i].y + .5);
        }

        
        
        // a slightly less efficient, but more reasonable way to compute minX and minY
        if( v[0].x < v[1].x ) 
        {
            minX = v[0].x < v[2].x ? 0 : 2;
            maxX = v[2].x > v[1].x ? 2 : 1;
        }
        else
        {
            minX = v[2].x < v[1].x ? 2 : 1;
            maxX = v[2].x > v[0].x ? 2 : 0;
        }
        midX = 3 - minX - maxX;

        if( v[0].y < v[1].y ) 
        {
            minY = v[0].y < v[2].y ? 0 : 2;
            maxY = v[2].y > v[1].y ? 2 : 1;
        }
        else
        {
            minY = v[2].y < v[1].y ? 2 : 1;
            maxY = v[2].y > v[0].y ? 2 : 0;
        }
        midY = 3 - minY - maxY;
        
    }
    
    public void Draw(Raster r) {
        
        double i, j;
        double xmin, xmax;
        
        // edges you walk down
        Edge leftEdge, rightEdge;
        // values for each of the colors
        double left_a, left_r, left_g, left_b, right_a, right_r, right_g, right_b;
        // current color used to calculate the colo of each pixel, before drawing
        int curr_color; double curr_a, curr_r, curr_g, curr_b;
        // change in color as edge is walked from left to right
        double da, dr, dg, db;
        // length of each row
        double len;
      
        // which side to switch on
        boolean switchLeft = false;
        boolean switchRight = false;
        
        // obtain the first set of scan values
        if( v[minY].y == v[midY].y )
        // case where the top is flat
        {
            // then xmin and xmax do not start at the same point
            xmin = v[minY].x < v[midY].x ? v[minY].x : v[midY].x;
            xmax = v[minY].x > v[midY].x ? v[minY].x : v[midY].x;

            // define the first set of dxL, and dxr (the change in the x value of the last 
            // edge and the right edge for each 1 change in y
            if( v[minY].x < v[midY].x )
            {
                leftEdge = new Edge( v[minY], v[maxY] );
                rightEdge = new Edge( v[midY], v[maxY] );
            }
            else
            {
                leftEdge = new Edge( v[midY], v[maxY] );
                rightEdge = new Edge( v[minY], v[maxY] );
            }
        }
        else
        {
            xmin = xmax = v[minY].x;

            // define the first set of dxL, and dxr (the change in the x value of the last 
            // edge and the right edge for each 1 change in y
            if( v[midY].x < (new Edge(v[minY], v[maxY])).getX(v[midY].y) )
            {
                leftEdge = new Edge( v[minY], v[midY] );
                rightEdge = new Edge( v[minY], v[maxY] );
                switchLeft = true;
            }
            else
            {
                leftEdge = new Edge( v[minY], v[maxY] );
                rightEdge = new Edge( v[minY], v[midY] );
                switchRight = true;
            }
        }

        // initialze the color counters
        left_a = leftEdge.a_start;
        left_r = leftEdge.r_start;
        left_g = leftEdge.g_start;
        left_b = leftEdge.b_start;
        right_a = rightEdge.a_start;
        right_r = rightEdge.r_start;
        right_g = rightEdge.g_start;
        right_b = rightEdge.b_start;
        
        
        for( j = v[minY].y; j <= v[maxY].y ; j++ )
        {
            len = xmax - xmin;
            da = (right_a - left_a) / len ;
            dr = (right_r - left_r) / len ;
            dg = (right_g - left_g) / len ;
            db = (right_b - left_b) / len ;
            curr_a = left_a;
            curr_r = left_r;
            curr_g = left_g;
            curr_b = left_b;

            for( i = xmin; i <= xmax ; i++ )
            {
                curr_color = getRGB( (int)(curr_a + .5), (int)(curr_r + .5), (int)(curr_g + .5), (int)(curr_b + .5));
                r.setPixel(curr_color, (int) i, (int) j );
                curr_a += da;
                curr_r += dr;
                curr_g += dg;
                curr_b += db;
            }
            
            // update edges
            xmin += leftEdge.dx;
            xmax += rightEdge.dx;
            left_a += leftEdge.da;
            left_r += leftEdge.dr;
            left_g += leftEdge.dg;
            left_b += leftEdge.db;
            right_a += rightEdge.da;
            right_r += rightEdge.dr;
            right_g += rightEdge.dg;
            right_b += rightEdge.db;
            
            if( j+1 == v[midY].y )
            {
                // change the dx and reset the xmin or xmax values at branch point
                if( switchLeft )
                {
                    leftEdge = new Edge( v[midY], v[maxY] );
                    xmin = v[midY].x;
                    left_a = leftEdge.a_start;
                    left_r = leftEdge.r_start;
                    left_g = leftEdge.g_start;
                    left_b = leftEdge.b_start;
                }
                else if( switchRight)
                {
                    rightEdge = new Edge( v[midY], v[maxY] );
                    xmax = v[midY].x;
                    right_a = rightEdge.a_start;
                    right_r = rightEdge.r_start;
                    right_g = rightEdge.g_start;
                    right_b = rightEdge.b_start;                    
                }
            }
        }
        
        //r.setPixel(v[0].argb, (int) v[0].x, (int) v[0].y);
        //r.setPixel(v[1].argb, (int) v[1].x, (int) v[1].y);
        //r.setPixel(v[2].argb, (int) v[2].x, (int) v[2].y);
    }
}