/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cnc.gcode.controller;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

/**
 *
 * @author Patrick
 */
public final class Geometrics {
    
    public static final double NONRATIO = 0;
    
    public enum EArea { UPPER_SIDE, LOWER_SIDE, RIGHT_SIDE, LEFT_SIDE, UL_CORNER, UR_CORNER, LL_CORNER, LR_CORNER, CENTER, NON  }
    
    /**
     * Creates a Rectangle out of two Points
     * @param p1
     * @param p2
     * @return 
     */
    public static Rectangle pointToRectangel(Point p1, Point p2){
        Rectangle rect =  new Rectangle();
        if(p1.x < p2.x)
        {
            rect.x      = p1.x;
            rect.width  = p2.x - p1.x;
        }
        else{
            rect.x      = p2.x;
            rect.width  = p1.x - p2.x;            
        }
        if(p1.y < p2.y){
            rect.y      = p1.y;
            rect.height = p2.y - p1.y;
        }
        else{
            rect.y      = p2.y;
            rect.height = p1.y - p2.y;            
        }
        return rect;
    }
    public static Rectangle2D pointToRectangel(Point2D p1, Point2D p2){
        double x,y,width,height;
        if(p1.getX() < p2.getX())
        {
            x = p1.getX();
            width = p2.getX() - p1.getX();
        }
        else{
            x = p2.getX();
            width = p1.getX()- p2.getX();            
        }
        if(p1.getY() < p2.getY()){
            y = p1.getY();
            height = p2.getY()-p1.getY();
        }
        else{
            y = p2.getY();
            height = p1.getY()-p2.getY();            
        }
        return new Rectangle2D.Double(x, y, width, height);
    }
    
    
    
    /**
     * Calcs where the point is located on the rectangle 
     * @param point
     * @param rectangle
     * @param margin
     * @return 
     * -1== out of range
     * 0 == in the window
     * 1 2 3 4 = Corners
     * 5 6 7 8 = side 
     *  ---> x
     * |   1 _____ 2
     * |    |  5  |
     * V   8|     |6
     * y    |  0  |
     *      '-----'
     *     4   7   3
     * 0=CENTER
     * 1=UL_CORNER
     * 2=UR_CORNER
     * 3=LR_CORNER
     * 4=LL_CORNER
     * 5=UPPER_SIDE
     * 6=RIGHT_SIDE
     * 7=LOWER_SIDE
     * 8=LEFT_SIDE
     * 
     */
    public static EArea pointOnRectangle(Point point, Rectangle rectangle, int margin){
        EArea area = EArea.NON;
        
        int x   = point.x;
        int y   = point.y;
        int r_x = rectangle.x;
        int r_y = rectangle.y;
        int r_w = rectangle.width;
        int r_h = rectangle.height;
        
        if(x > r_x 
        && y > r_y 
        && x < (r_x + r_w) 
        && y<(r_y + r_h))
        {
            area = EArea.CENTER;
        }
        if(x >= (r_x-margin) &&  x <= (r_x + r_w + margin))
        {
            if(Math.abs(y - r_y) <= margin)
            {
                area = EArea.UPPER_SIDE;
            }
            if(Math.abs(y - r_y - r_h) <= margin)
            {
                area = EArea.LOWER_SIDE;
            }
        }
        if(y > (r_y-margin) &&  y < (r_y + r_h + margin))
        {
            if(Math.abs(x - r_x) <= margin)
            {
                area = EArea.LEFT_SIDE;
            }
            if(Math.abs(x-r_x-r_w) <= margin)
            {
                area = EArea.RIGHT_SIDE;
            }
        }
        if(Math.abs(x - r_x) <= margin && Math.abs(y - r_y) <= margin)
        {
            area = EArea.UL_CORNER;
        }
        if(Math.abs(x - r_x - r_w) <= margin && Math.abs(y - r_y) <= margin)
        {
            area = EArea.UR_CORNER;
        }
        if(Math.abs(x - r_x - r_w) <= margin && Math.abs(y - r_y - r_h) <= margin)
        {
            area = EArea.LR_CORNER;
        }
        if(Math.abs(x - r_x) <= margin && Math.abs(y - r_y - r_h) <= margin)
        {
            area = EArea.LL_CORNER;
        }
        
        return area;
    }
    
    /** Returns true if the Point is over the Rectangle within a
     * @param point
     * @param margin
     * @param rectangle
     * @return */
    public static boolean pointInRectangle(Point point, Rectangle rectangle, int margin){
        int x   = point.x;
        int y   = point.y;
        int r_x = rectangle.x;
        int r_y = rectangle.y;
        int r_w = rectangle.width;
        int r_h = rectangle.height;
        if((x + margin) >= r_x && (y + margin) >= r_y 
            && (x - margin) <= (r_x + r_w) 
            && (y - margin) <= (r_y + r_h))
        {
            return true;
        }
        return false;
    }

    public static boolean pointInRectangle(Point2D point, Rectangle2D rectangle, double margin){
        double x    = point.getX();
        double y    = point.getY();
        double r_x  = rectangle.getX();
        double r_y  = rectangle.getY();
        double r_w  = rectangle.getWidth();
        double r_h  = rectangle.getHeight();
        if((x + margin) >= r_x 
            && (y + margin) >= r_y && (x - margin) <= (r_x + r_w)
            && (y - margin) <= (r_y + r_h))
        {
            return true;
        }
        return false;
    }

    
    /** Returns true if the two Rectangle are overlaping
     * @param r1
     * @param r2
     * @return */
    public static boolean rectangleInRectangle(Rectangle r1, Rectangle r2){
        if(r1.x < (r2.x + r2.width) 
       && (r1.x + r1.width) > r2.x && r1.y < (r2.y + r2.height) 
       && (r1.y + r1.height)> r2.y)
        {
            return true;
        }
        return false;
    }
    
    
    public static double getRatio(double w, double h){
        return (double)w / h;
    }
    public static double getRatio(int w, int h){
        return (double)w / h;
    }
    
    /**
     * Returns a Rectangle placed centered in (0;w,0:h) with the maximum possible size and the given ratio 
     * @param w
     * @param h
     * @param ratio
     * @return 
     */
    public static Rectangle placeRectangle(int w, int h, double ratio){                
        int w_neu   = w;
        int h_neu   = h;

        if(ratio == NONRATIO)
        {
            ratio = getRatio(w,h);
        }

        if(ratio < getRatio(w,h))
        {
            w_neu = (int)(ratio * h);
        }
        else
        {
            h_neu = (int)(w / ratio);
        }                  

        return new Rectangle((w - w_neu) / 2,
                            (h - h_neu) / 2,
                            w_neu,
                            h_neu);
    }
    
    /**
     * Calces the scale for (w2,h2) to fit into (w1,h1) 
     */
    public static double getScale(double w1, double h1, double w2, double h2){
        if(getRatio(w1,h1) < getRatio(w2,h2))
        {
            return (double)w1 / w2;
        }            
        else
        {
            return (double)h1 / h2;
        }            
    }

    public static double getScale(int w1, int h1, int w2, int h2){
        return getScale((double)w1,
                        (double)h1,
                        (double)w2,
                        (double)h2);
    }

    
    /**
     * Manipulate rectangle (old) (defined in Operation) with delta. Allowed area is (0,max.x;0,max.y)
     * returns new rectangle
     * @param operation
     * @param oldR
     * @param delta
     * @param ratio
     * @param max
     * @return 
     */
    @SuppressWarnings("fallthrough")
    public static Rectangle manipulateRectangle(EArea operation,
                                                Rectangle oldR,
                                                Point delta,
                                                Point max,
                                                double ratio){
        Rectangle r = new Rectangle(oldR);

        //Spacel Case Center:
        if(operation == EArea.CENTER)
        {
            r.x += delta.x;
            r.y += delta.y;
            if(r.x < 0)
            {
                r.x = 0;
            }
            if(r.y < 0)
            {
                r.y=0;
            }
            if(r.x + r.width >= max.x)
            {
                r.x = max.x - r.width;
            }                                      
            if(r.y + r.height >= max.y)
            {
                r.y = max.y - r.height;
            }    
            return r;
        }
        
        
        int maxWidth;
        int maxHeight;
        boolean editX   = false;
        boolean editY   = false;
        
        switch(operation){
            case RIGHT_SIDE:    // 
            case LR_CORNER:     // --->                
            case UR_CORNER:     //                
                r.width += delta.x; 
            case UPPER_SIDE:                      
            default:
                maxWidth = max.x-r.x;
                break;
                
            case LEFT_SIDE:     //                 
            case LL_CORNER:     // <---
            case UL_CORNER:     //               
                r.width -= delta.x;                
            case LOWER_SIDE: 
                maxWidth = oldR.x + oldR.width;
                editX    = true;
                break;
        }
        switch(operation){
            case LOWER_SIDE:   // |                   
            case LL_CORNER:    // |
            case LR_CORNER:    // V                 
                r.height    += delta.y; 
            case RIGHT_SIDE:
            default:
                maxHeight   = max.y - r.y;
                break;
                
            case UPPER_SIDE:   // ^                  
            case UL_CORNER:    // |               
            case UR_CORNER:    // |                 
                r.height    -= delta.y;   
            case LEFT_SIDE:                      
                maxHeight   = oldR.y + oldR.height;
                editY       = true;
                break;        
        }
               
        //Calc possible range for width and height
        Rectangle range = placeRectangle(maxWidth, maxHeight, ratio);
            
        //Calc ratio
        if(ratio != NONRATIO)
        {
            switch(operation)
            {
                case LEFT_SIDE:
                case UL_CORNER:
                case RIGHT_SIDE:
                case LR_CORNER:
                    r.height = (int)Math.floor(r.width / ratio);
                default:
                    r.width = (int)Math.floor(r.height * ratio);
            }
        }
        
        //Overflow
        if(r.width > range.width)
        {
            r.width = range.width;
            if(ratio != NONRATIO)
            {
                r.height = range.height;
            }
        }
        if(r.height > range.height)
        {
            r.height = range.height;
            if(ratio != NONRATIO)
            {
                r.width = range.width;
            }        
        }

        //Underflow 
        if(r.width <= 0)
        {
            r.width = 1;
        }
        if(r.height <= 0)
        {
            r.height = 1;
        }
        

        if(editX)
        {
            r.x = oldR.x + oldR.width - r.width;
        }
        
        if(editY)
        {
            r.y = oldR.y + oldR.height - r.height;
        }
        
        return r;
    }
    
    /**
     * Returns the proper Cursor for the given area! 
     * @param area
     * @return 
     */
    public static Cursor getCursor(EArea area){
        switch(area){
            case CENTER:                      
              return Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR);
            case RIGHT_SIDE:                      
              return Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR);
            case LEFT_SIDE:                      
              return Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR);
            case UPPER_SIDE:                      
              return Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR);
            case LOWER_SIDE:                      
              return Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR);
            case LL_CORNER:                      
              return Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR);
            case LR_CORNER:                      
              return Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR);
            case UL_CORNER:                      
              return Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR);
            case UR_CORNER:                      
              return Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR);
            default:
              return Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
        }
    }
    
    /**
     * Bounds the point in the Rectangle!
     * @param t_pos
     * @param rect
     * @return 
     */
    public static Point limitPointInRect(Point t_pos, Rectangle rect){
        Point pos = t_pos.getLocation();
        pos.x = Tools.adjustInt(pos.x, rect.x, (rect.x + rect.width));
        pos.y = Tools.adjustInt(pos.y, rect.y, (rect.y + rect.height));

        return pos;
    }
    
    /**
     * Draws a Rectangle with a dashed Line
     * @param g
     * @param r
     * @param c1
     * @param c2 
     */
    public static void drawDashedLine(Graphics g, Rectangle r, Color c1, Color c2){
        g.setColor(c1);
        g.drawRect( r.x,
                    r.y,
                    r.width,
                    r.height);
        if(g instanceof Graphics2D)
        {
            ((Graphics2D)g).setStroke(new BasicStroke(1,BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL,10.0f,new float[]{ 5f }, 0f));
            g.setColor(c2);
            g.drawRect( r.x,
                        r.y,
                        r.width,
                        r.height);
            ((Graphics2D)g).setStroke(new BasicStroke());
        }        
    }
    
    /**Returns a Rectangle witch contains all rectangles
     * @param rects
     * @return  */
    public static Rectangle compRectangles(Rectangle[] rects){
        Rectangle ret = new Rectangle(-1, -1);
        for(Rectangle rect: rects)
        {
            ret.add(rect);
        }
        return ret;
    }


    /**Converts a dimension to a point x=width y=heig
     * @param d
     * @return */
    public static Point dimensiontoPoint(Dimension d)
    {
        return new Point(d.width, d.height);
    }
    
    /** Scale the element Rectangl
     * @param element
     * @param r_old
     * @param r_new
     * @param elemente
     * @return */
    public static Rectangle scaleRectangleInRectangle(Rectangle element,
                                                      Rectangle r_old,
                                                      Rectangle r_new){
        if (r_new == null)
        {
            return element;
        }
        
        Rectangle inner = new Rectangle(element.x - r_old.x,
                                        element.y - r_old.y,
                                        element.width,
                                        element.height);
        
        double s_x = (double)r_new.width / r_old.width;
        double s_y = (double)r_new.height / r_old.height;
        
        int width   = (int)(inner.width * s_x);
        int height  = (int)(inner.height * s_y);
        if (width <= 0)
        {
            width = 1;
        }
        if (height <= 0)
        {
            height = 1;
        }
        
        inner = new Rectangle((int)(inner.x * s_x),
                              (int)(inner.y * s_y),
                               width,
                               height);

        return new Rectangle(inner.x + r_new.x,
                             inner.y + r_new.y,
                             inner.width,
                             inner.height);
    }
    
    public static Rectangle scaleRectangleandPos(Rectangle r,
                                                 Point center,
                                                 double scale){
        return new Rectangle((int)(r.x * scale) + center.x,
                             (int)((r.y) * scale) + center.y,
                             (int)(r.width * scale),
                              (int)(r.height * scale));
    }
    
    
    public static boolean doubleEquals(double a, double b, double epsilon)
    {
        double f = a - b;
        if(f <= ( 0 - epsilon ) )
        {
            return false;
        }
        if(f >= ( 0 + epsilon ) )
        {
            return false;
        }
        return true;
    }
    
    public static boolean doubleEquals(double a, double b)
    {
        double epsilon = 0.0000001;
        return doubleEquals(a, b, epsilon);
    }
    
    public static double getDistance(double x1, double y1, double x2, double y2)
    {
        return Math.hypot(x1 - x2, y1 - y2);
    }

    private Geometrics(){
        throw new AssertionError();
    }
}