import java.io.BufferedReader;
import java.io.FileReader;

import java.awt.geom.GeneralPath;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.geom.Line2D;
import javax.swing.*;
import java.awt.*;
import java.util.LinkedList;
/**
    Class Router
    class that provides easy interface to other router classes
**/

public class Router
{
    /********************************/
    int w, h;
    public LinkedList routes;
    
    /********************************/
    Open open;
    Closed closed;
	Map map;
    float colorScale;
    BufferedImage mapimage;
    private CoordTransform coordTransform = new CoordTransform();
    /** 
       constructor
       @param w the width of the map
       @param h the height of the map
    **/ 
    
	public Router( int w, int h, int ULx, int ULr, int LRx, int LRy,
            int mapULx, int mapULy )
	{
        this.w = w;
        this.h = h;
        map = new Map ( w, h );
        routes = new LinkedList();
        mapimage = null;
        coordTransform = new CoordTransform( ULx, ULr, LRx, LRy, mapULx,
                mapULy, w , h );
	}

    /** 
        generates some number of random radar
        @param numRadar number of radar to place     
    **/
    
    public void genRandomRadar( int numRadar ) { 
        for ( int i = 0; i < numRadar; i ++ )
            map.genRandomRadar();
        updateMap();
    }

    /**
        add a radar to the map
        @param x x position of the radar
        @param y y position of the radar
        @param r radius of the radar's coverage
        @param s Strength : danger the radar presents inside its coverage
    **/

    public void addRadar( Radar newRadar ) {
//        ScreenLoc currentSL = newRadar.GetScreenLoc();
//        ScreenLoc tempSL = new ScreenLoc( (int)coordTransform.transformx( currentSL.GetX()),
//            (int)coordTransform.transformy( currentSL.GetY()));
//        newRadar.SetScreenLoc(tempSL);
//        newRadar.SetRadius( (double)coordTransform.scalex( (float)newRadar.GetRadius()));
        map.addRadar(newRadar );
        updateMap();
    }
    
//    public ScreenLoc convertToScale( ScreenLoc newSL ){
//        ScreenLoc tempSL = new ScreenLoc( (int)coordTransform.transformx( newSL.GetX()),
//            (int)coordTransform.transformy( newSL.GetY()));
//        return tempSL;
//    }

    /**
        propogates changes to map configuration to all others
    **/

    private void updateMap(){
        map.update();
        colorScale = 1.0f / map.maximumDanger();
        mapimage = new BufferedImage( w, h, BufferedImage.TYPE_INT_RGB );
        for( int i = 0; i < w; i ++ )
            for( int j = 0; j < h; j ++ )
            {
                float red = map.dangerAt( i, j ) * colorScale;
                float green = red;
                float blue = red;
                Color color = new Color( red, green, blue );
                mapimage.setRGB( i, j, color.getRGB() );
            }
    }

    /**
        finds a given route
        the route is first calculated through a* then ran through a line smoother.
        @param x0 x coordinate of starting location
        @param y0 y coordinate of starting location
        @param x1 x coordinate of goal
        @param y1 y coordinate of goal
        @return Linked list of locations representing wp's to follow.
    **/
    public LinkedList findRoute( int x0, int y0, int x1, int y1 )
	{
        Location start = new Location( x0, y0 );
        Location goal = new Location( x1, y1 );
        Graph graph = new Graph( map );
        
        LinkedList route = graph.aStarSearch( start, goal );
        open    = graph.getOpen();
        closed  = graph.getClosed();
        
        route = map.smoothRoute( route ); 
        routes.add( route );
        System.out.println( "Total danger : " + map.dangerOnRoute( route ) );
        return route;
    }
    
     public double findRouteDanger( int x0, int y0, int x1, int y1 ){
        Location start = new Location( x0, y0 );
        Location goal = new Location( x1, y1 );
        Graph graph = new Graph( map );
        
        LinkedList route = graph.aStarSearch( start, goal );
        open    = graph.getOpen();
        closed  = graph.getClosed();
        
        route = map.smoothRoute( route ); 
        routes.add( route );
        System.out.println( "Total danger : " + map.dangerOnRoute( route ) );
        return map.dangerOnRoute( route );
    }

    public void findRoute( float x0, float y0, float x1, float y1 ) {
        findRoute( (int) x0, (int) y0, (int) x1, (int) y1 );
    }
    public void findRoute( double x0, double y0, double x1, double y1 ) {
        findRoute( (int) x0, (int) y0, (int) x1, (int) y1 );
    }

    /**
        draws the map image until the graphics object
        do appropriate scaling between w,h and window w,h first
        @param _g the graphics object to paint too
    **/
    public void paintMap( Graphics _g ) {
        Graphics2D g = (Graphics2D) _g;
        g.drawImage( mapimage, new AffineTransform(1f,0f,0f,1f,0,0), null ); // dont ask, just paints that image in
    }
    
    /**
        draws all routes to g under different colors
        @param _g the graphics object to paint too
    **/
    public void paintRoutes( Graphics _g ) {
        int i;
        Graphics2D g = (Graphics2D) _g;
        for( i = 0; i < routes.size(); i ++ ){
            int j = i%6;
            switch( j ){
                case( 0 ): g.setColor( Color.blue ); break;
                case( 1 ): g.setColor( Color.cyan ); break;
                case( 2 ): g.setColor( Color.green ); break;
                case( 3 ): g.setColor( Color.magenta ); break;
                case( 4 ): g.setColor( Color.orange ); break;
                case( 5 ): g.setColor( Color.pink ); break;
                default:   g.setColor( Color.yellow ); break;
            }
            paintRoute( i , _g );
        }
    }

    /**
        draws route[routeIndex] to g with the current color
        @param routeIndex the route to draw, index starts at 0 incremented with each new route.
        @param _g the graphics object to paint too
    **/

    public void paintRoute( int routeIndex, Graphics _g ) { 
        paintRoute( (LinkedList) routes.get( routeIndex ), _g );
    }

    public void paintRoute( LinkedList route, Graphics _g ) {
        Graphics2D g = (Graphics2D) _g;
        for( int j = 1; j < route.size(); j ++ ){
            Location a = (Location) route.get( j-1 );
            Location b = (Location) route.get( j );
            Line2D.Float l = new Line2D.Float( a.x, a.y, b.x, b.y );
            g.draw( l );
        }
    }

    /**
        draws a spline along route[routeIndex]
        doesn't work quite right because it uses bezier splines, need to hardcode catmull-rom
    **/

    public void paintSplineRoute( int routeIndex, Graphics _g ) {
        Graphics2D g = (Graphics2D) _g;
        LinkedList route = (LinkedList) routes.get( routeIndex );
        GeneralPath path = new GeneralPath();
        Location a = (Location) route.get( 0 );
        Location b = (Location) route.get( 1 );
        Location c = (Location) route.get( 2 );
        path.moveTo( a.x, a.y );
        
        for( int i = 1; i < route.size()-1; i ++ ){
            a = (Location) route.get( i-1 );
            b = (Location) route.get( i+1 );
            c = (Location) route.get( i );
            path.curveTo(
                            a.x, a.y,
                            b.x, b.y,
                            c.x, c.y 
                        );
        }
        path.curveTo ( 
                        b.x, b.y,
                        c.x, c.y,
                        c.x, c.y    
                     );
        g.draw( path );
    }

    /**
        draws the part of the search tree that is in the open set to g under the current color
        the open set is the set of nodes from the last routing that were still being considered for expansion when the goal was found
        @param _g the graphics object to paint too
    **/
    
    public void paintOpen( Graphics _g ) {
        if( open == null ) return;
        LinkedList nodes = new LinkedList( open.values() );
        drawNodes( nodes, _g );
    }

    /**
        draws the part of the search tree that is in the closed set under the current color
        the closed set is the set of all nodes from the last routing that had been fully expanded
        @param _g the graphics object to paint too
    **/
    public void paintClosed( Graphics _g ) {
        if( closed == null ) return;
        LinkedList nodes = new LinkedList( closed.values() );
        drawNodes( nodes, _g );
    }

    /**
        draws some LinkedList of nodes to g
        @param _g the graphics object to paint too
    **/

    void drawNodes( LinkedList nodes, Graphics _g ) {
        Graphics2D g = (Graphics2D) _g;
        for( int i = 0; i < nodes.size(); i ++ ) {
            Node node = (Node) nodes.get( i );
            if( node.parent!= null){
                Line2D.Float line = new Line2D.Float(
                    node.parent.location.x,
                    node.parent.location.y,
                    node.location.x,
                    node.location.y );
                g.draw( line );
            }
        }
    }

    private float toFloat( String a )    {
        return Float.valueOf( a ).floatValue();
    }

    private int toInt( String a )    {
        return Integer.valueOf( a ).intValue();
    }

    private boolean comp( String a, String b ) {
        return ( a.indexOf( b ) != -1 );
    }

    
    /**
        loadFromFile
        loads a map from a file, from whatever format i end up deciding on
    **/

    public void loadFromFile ( String filename )
    {
        CoordTransform coordTransform = new CoordTransform();
        BufferedReader in;
        try { 
           in = new BufferedReader( new FileReader( filename )) ;
        } catch ( Exception e ) {
            System.out.println( "File Open Failed" ); return; }

        String line;
        String var;
        String[] s;
    
        int width = w;
        int height = h;
        
        class RadarData{
            public float x, y,r,s;
            public RadarData( float x, float y, float r, float s ) {
                this.x = x;
                this.y = y;
                this.r = r;
                this.s = s;
            }
        }
        
        LinkedList radars = new LinkedList();
        int randomRadar = 0;
        try {
            while( ( line = in.readLine() ) != null )
            {
                s = line.split(" ");
                var = s[0].toLowerCase();
                if( comp( var, "transform" ) )
                    coordTransform = new CoordTransform(    toFloat( s[1] ),
                                                            toFloat( s[2] ),
                                                            toFloat( s[3] ),
                                                            toFloat( s[4] ),
                                                            toFloat( s[5] ),
                                                            toFloat( s[6] ),
                                                            toFloat( s[7] ),
                                                            toFloat( s[8] ) );
                else if( comp( var, "width" ) )
                    width = toInt( s[1] );
                else if( comp( var, "height" ) )
                    height = toInt( s[1] );
                else if( comp( var, "randomradar" ) )
                    randomRadar = toInt( s[1] );
                else if( comp( var, "radar" ) )
                    radars.add( new RadarData(  coordTransform.transformx( toFloat( s[1] ) ),
                                                coordTransform.transformy( toFloat( s[2] ) ),
                                                coordTransform.scalex( toFloat( s[3] ) ),
                                                toFloat( s[4] ) ) );
//                                                coordTransform.scaley( toFloat( s[4] ) )    )   );
                else if( comp( var, "creativity" ) )
                    Const.setCreativity( toFloat( s[1] ));
                else if( comp( var, "ambientdanger" )) 
                    Const.setAmbientDanger( toFloat( s[1] ));
                else if( comp( var, "smootherbias" ))
                    Const.setSmootherBias( toFloat( s[1] ));
                else if( comp( var, "distancelinesegments" ) )
                    Const.setDistanceLineSegments( toInt( s[1] ));
                else if( comp( var, "distanceestimatetype" ))
                    Const.setDistanceEstimateType( toInt( s[1] ));
                else
                    System.out.println( var + " not defined" );
            }
        } catch ( Exception e ) { System.out.println( "File reading failed" ); e.printStackTrace(); return; }
        this.w = width;
        this.h = height;
        System.out.println( w + " " + h + " " + radars.size() );  

        map = new Map( w, h );
        routes = new LinkedList();
        for( int i = 0; i < radars.size(); i ++ ){ 
            RadarData data = (RadarData) radars.get( i );
            System.out.println( "Radar " + data.x + " " + data.y + " " + data.r + " " + data.s );
//            map.addRadar( data.x, data.y, data.r, data.s );
        }
        genRandomRadar( randomRadar );
        updateMap();
    }
}
