package clothSimulator;



import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.File;
import java.text.DecimalFormat;
import java.text.NumberFormat;

import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.border.TitledBorder;
import javax.vecmath.Vector3d;

import meshTools.PolygonSoup;
import mintools.parameters.IntParameter;

import comp599.a1.numerical.Integrator;
import comp599.a1.numerical.SymplecticEuler3D;
import comp599.tools.parameters.BooleanParameter;
import comp599.tools.parameters.DoubleParameter;
import comp599.tools.swing.CollapsiblePanel;
import comp599.tools.swing.VerticalFlowPanel;
import comp599.tools.viewer.BoxRoom;
import comp599.tools.viewer.EasyViewer;
import comp599.tools.viewer.Interactor;
import comp599.tools.viewer.SceneGraphNode;
import comp599.tools.viewer.ShadowMap;

/** This is program intended to implement a somewhat general constrained mechanics 
 * animator.  I have called it clothSimulation because one of the key goals will be to
 * simulate cloth, however, the solver will be set up to handle several kinds of constraints
 * not just distance or stretch constraints. 
 */
public class clothSimulation implements  Interactor, SceneGraphNode {

    /**
     * Entry point for application
     * 
     * @param args
     */
    public static void main(String[] args) {
        new clothSimulation();
    }

    private EasyViewer ev;
    
    /**
     * The particle system is public because the cloth experiments need access
     */
    public ParticleSystem system;
    
    //private Integrator forwardEuler = new ForwardEuler(false);
    
    private Integrator theIntegrator = new SymplecticEuler3D(false);
    
    private int systemType=2;
    
    /**
     * Creates a shadow map 1024x1024.  This number can be reduced to improve performance
     * but probably can't go too much higher.
     */
    private ShadowMap shadowMap = new ShadowMap( 1024 );

    /**
     * Creates the application / scene instance
     */
    public clothSimulation() {
        system = new ParticleSystem();
        //you pass in a number which corresponds to which type of particle system you want
        system.createSystem(systemType);
        system.integrator = theIntegrator;
        
//        //ev = new EasyViewer("KBTest", this, new Dimension(640, 480), new Dimension(640, 640));
        ev =  new  EasyViewer("KBTest", this, new Dimension(640, 480), new Dimension(640, 640),new Vector3d(0,10,10) , new Vector3d(0,10,-10) );
        ev.addInteractor(this);        
        ev.controlFrame.add("Shadow", shadowMap.getControls() );        
    }
    
    @Override
    public void init(GLAutoDrawable drawable) {
        GL gl = drawable.getGL();
        
        // initial set up for smooth lines and points
        gl.glEnable(GL.GL_BLEND);
        gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
        gl.glEnable(GL.GL_LINE_SMOOTH);
        gl.glEnable(GL.GL_POINT_SMOOTH);

        shadowMap.init(drawable);

        boxRoom.init(drawable);

        // we'll make the room 20 meters by 20 meters and 
        boxRoom.setBoundingBox(new Vector3d(-30, 0, -30), new Vector3d(30, 60, 30));
    }

    private BoxRoom boxRoom = new BoxRoom();

    /**
     * Size of the displayed markers in mm
     */
    private DoubleParameter markerSize = new DoubleParameter("marker size", 10, 1, 100);

    /**
     * A variable for starting and stopping the animation. The space bar in the
     * display window will toggle this.
     */
    public BooleanParameter play = new BooleanParameter("play animation",false);
    /**
     * The current half edge to display (to help with debugging)
     */
    private meshTools.HalfEdge currentHE;
    
    /** a half edge data structure for nicely drawing the sheet */
    meshTools.HEDS[] heds;
    
    /** sets the maximum subdivision levels */
    int maxSubLevels=0;
    
    /** for displaying a subdivided mesh */
    PolygonSoup theSoup;
    
    @Override
    public void display(GLAutoDrawable drawable) {
        if ( play.getValue() || steprequest ) {
            steprequest = false;
            system.advanceTime(h.getValue(), imposeConstraints, applyBaumgarte,applyPostStep);
            system.fixClothNormals();
            stepped = true;        
        }
        
        if ( ! drawWithShadows.getValue() ) {
            drawAllObjects(drawable);
        } else {        
            GL gl = drawable.getGL();            
            // disable lights enabled by the easy viewer... the shadow map will enable different lights...
            gl.glDisable( GL.GL_LIGHT0 );
            gl.glDisable( GL.GL_LIGHT1 );
            
            shadowMap.beginLightPass( drawable );
            drawAllObjects(drawable);        
            shadowMap.endLightPass( drawable );
                    
            // Draw ambient only...        
            shadowMap.beginAmbientPass( drawable );         
            drawAllObjects( drawable );
            shadowMap.endAmbientPass( drawable );
    
            // Draw diffuse with shadows
            shadowMap.beginShadowMapping(drawable); 
            drawAllObjects( drawable );
            shadowMap.endShadowMapping( drawable );
     
            // Draw debugging information (Frustum, shadow map)
            shadowMap.displayDebug( drawable );
        }
                 
        EasyViewer.beginOverlay(drawable);
        String text = "\nBen Sprott 260305463  "+"\n time step:" +h.getValue();
        EasyViewer.printTextLines(drawable, text);
        EasyViewer.endOverlay(drawable);
        
        if ( play.getValue() || stepped ) {
        	stepped = false;
            if ( record.getValue() ) {
                // write the frame
                File file = new File( "C:/Documents and Settings/bensprott/My Documents/workspace/Animation/stills/" + dumpName + format.format(nextFrameNum) + ".png" );                                             
                 nextFrameNum++;
                file = new File(file.getAbsolutePath().trim());
                ev.snapshot(drawable, file);
            }
        }
    }

    /**
     * Draw the scene, but does not do any stepping or overlays or other drawing.
     * This call is made multiple times to render with shadows
     */
    private void drawAllObjects( GLAutoDrawable drawable ) {
        GL gl = drawable.getGL();
        
        gl.glPushMatrix();        
        // this is arbitrary to try to make thing look nice
        gl.glScaled( 0.15, 0.15, 0.15 );
        
        if(drawBoxRoom.getValue()) {
            boxRoom.display(drawable);
        }
        
        gl.glPopMatrix();
        gl.glPushMatrix();
        // likewise, another arbitrary scale to try to get things to look nice...
        double t = clothTranslatey.getValue();
        gl.glTranslated( 0, t, 0 );
        double s = clothScale.getValue();
        gl.glScaled(s,s,s);
        
        // Here we do the drawing and that includes the option to convert the whole thing into a polygon soup and then
        // subdivide the mesh before drawing it.
                 
        // theSoup=system.SheetToPolygonSoup();
        if(displayConstraintsNSprings.getValue()){
            system.display(drawable);   
        } else if (systemType==2){
            gl.glDisable(GL.GL_CULL_FACE);
            gl.glEnable(GL.GL_LIGHTING);
            float[] frontColor = { 1, 1.0f, 0, 1 };
            float[] backColor = { 1, 0f, 1f, 1 };
            gl.glLightModeli(GL.GL_LIGHT_MODEL_TWO_SIDE, 1);
            gl.glMaterialfv(GL.GL_FRONT, GL.GL_DIFFUSE, frontColor, 0);
            gl.glMaterialfv(GL.GL_BACK, GL.GL_DIFFUSE, backColor, 0);
            gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL);
            
            int levels=1;
            heds = new meshTools.HEDS[levels];
            heds[0] = new meshTools.HEDS(theSoup);
            currentHE = null;
            if ( heds[0].faces.size() > 0 ) {
                currentHE = heds[0].faces.get(0);
            }
            if(drawCurrentHE.getValue()){
                currentHE.display(drawable);
            }
            for ( int i = 1; i < levels; i++ ) {
                heds[i] = meshTools.Loop.subdivide( heds[i-1] );
            }
            currentHE = null;
            if ( heds[0].faces.size() > 0 ) {
                currentHE = heds[0].faces.get(0);
            }
            heds[levels-1].display(drawable);
//          if(displayWireFrame.getValue()){
//              
//          }else{
//              
//          }
        }
        gl.glPopMatrix();
    }

    /** we need to test the mesh so we need to display the current half edge */

    private BooleanParameter drawCurrentHE= new BooleanParameter("Draw Current Half Edge?", false); 
    /** a boolean to decide if we are going to draw the constraints or maybe a subdivided mesh 
     */
    private BooleanParameter displayConstraintsNSprings= new BooleanParameter("Display Constraints and Springs?", true);
    /**Boolean to display the wireframe subdivided mesh or draw the whole subdivided mesh with triangles
     */
    //private BooleanParameter displayWireFrame= new BooleanParameter("Display mesh wireframe?", true);
    /** the integer which tells us which subdivision level to draw */
    private IntParameter drawWhichSubdivLevel= new IntParameter("Draw which Subdivision level?",0,0,maxSubLevels);

    /** 
     * boolean to signal that the system was stepped and that a 
     * frame should be recorded if recording is enabled
     */
    private boolean stepped = false;
    
    /**
     * Flag to request a step, public for use by the cloth sim experiment class 
     */
    public boolean steprequest = false;
    
    private BooleanParameter record = new BooleanParameter( "record (press ENTER in canvas to toggle)", false );

    private String dumpName = "dump";
    
    private int nextFrameNum = 0;
    
    private NumberFormat format = new DecimalFormat("00000");
    
//    private BooleanParameter useg = new BooleanParameter( "use gravity", true );
  
    private JTextArea comments = new JTextArea("Constraint Based Animation with Stabilization:  Single and Double Pendulum");
    
    private BooleanParameter imposeConstraints=new BooleanParameter("Apply constraints", true);
    
    private BooleanParameter applyBaumgarte = new BooleanParameter("Apply  baumgarte stabilization", true);
    
    private BooleanParameter applyPostStep = new BooleanParameter("Apply post step stabilization", false);
    

    /** draw the box room?*/
    private BooleanParameter drawBoxRoom = new BooleanParameter("Draw the box room?",true);

    
    private BooleanParameter drawWithShadows = new BooleanParameter( "draw with shadows", true );
    
    private DoubleParameter clothScale = new DoubleParameter( "cloth scale", 0.5, 1e-2, 1 );
    private DoubleParameter clothTranslatey = new DoubleParameter( "cloth translate y", -6, -25, 25 );
    
    /**
     * The time step of the simulation
     */
    public DoubleParameter h = new DoubleParameter("Time Step", 0.006,1e-3,1);
        
    JLabel savedTrajectorySize = new JLabel("count = 0");

    @Override
    public JPanel getControls() {
        VerticalFlowPanel vfp = new VerticalFlowPanel();
        vfp.add( record.getControls() );
        vfp.add( savedTrajectorySize );
        vfp.add(play.getControls());
        vfp.add( drawWithShadows.getControls() );
        vfp.add( clothScale.getSliderControls(true) );
        vfp.add( clothTranslatey.getSliderControls(false) );
        
        JButton create1=new JButton("Simple, Spring Pendulum");
        vfp.add(create1);
        create1.addActionListener(new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e) {
            	newSystem(4);
            }
        });
        
        JButton create2=new JButton("Simple, Constraints Pendulum");
        vfp.add(create2);
        create2.addActionListener(new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e) {
            	newSystem(3);
            }
        });
        
        JButton create3=new JButton("n-Link chain");
        vfp.add(create3);
        create3.addActionListener(new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e) {
            	newSystem(1);
            }
        });
        
        JButton create4= new JButton("2-D Grid");
        vfp.add(create4);
        create4.addActionListener(new ActionListener(){
        	@Override
        	public void actionPerformed(ActionEvent e){        		
        		newSystem(2);
        	}
        });
        
        JButton create5= new JButton("T tester");
        vfp.add(create5);
        create5.addActionListener(new ActionListener(){
        	@Override
        	public void actionPerformed(ActionEvent e){
        		newSystem(5);
        	}
        });
        
        VerticalFlowPanel vfpScale = new VerticalFlowPanel();
        vfpScale.add(markerSize.getSliderControls(true));
        vfpScale.setBorder(new TitledBorder("Data scaling controls"));
        CollapsiblePanel cp1 = new CollapsiblePanel(vfpScale.getPanel());
        cp1.collapse();
        vfp.add(cp1);
        //drawing parameters
       // vfp.add(displayConstraintsNSprings.getControls());
       // vfp.add(displayWireFrame.getControls());
        vfp.add(drawWhichSubdivLevel.getSliderControls());
        //particle system controls
        vfp.add( system.getControls() );
        vfp.add( comments );
        vfp.add( imposeConstraints.getControls() );
        vfp.add( applyBaumgarte.getControls() );
        vfp.add( applyPostStep.getControls() );
        vfp.add(h.getSliderControls(true));
        vfp.add(drawBoxRoom.getControls());
        return vfp.getPanel();
    }

    
    
    @Override
    public void attach(Component component) {
        component.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_SPACE) {
                    play.setValue(!play.getValue());
                }
                if(e.getKeyCode()== KeyEvent.VK_R){
                	//System.out.println("");
                	resetSystem();
                } else if ( e.getKeyCode() == KeyEvent.VK_S ) {
                	steprequest = true;
                } else  if(e.getKeyCode()== KeyEvent.VK_C){
                	//System.out.println("");
                	//clearSystem();
                }
                if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                    ev.stop(); // quit
                }
                if (e.getKeyCode() != KeyEvent.VK_ESCAPE)
                    ev.redisplay();
            }
        });
    }

    
    
    
    /**
     * resets the system, currently by just recreating the n-link
     */


    /**
     * resets the system, currently by just recreating the n-link
     */

    public void resetSystem(){
//    	this.system=new ParticleSystem();
//        system.createSystem(systemType);
//        system.integrator = theIntegrator;
    	system.resetFrame();
    	system.resetParticles();
    	system.resetMST();
    	
    }
    


    /** 
     * clears the system
     * @param type
     */

    /** 
     * clears the system
     * @param type
     */

    public void newSystem(int type){
    	//this.system=new ParticleSystem();
    	system.resetFrame();
    	system.clearParticles();
        system.createSystem(type);
        system.resetMST();
        system.integrator = theIntegrator;
    }
    
    /**
     * @return true if Baumgarte stabilization is enabled
     */
    public Boolean get_applyBaumgarte(){
    	return applyBaumgarte.getValue();
    }
    
    /** 
     * @return true if post step stabilization is enabled
     */
    public Boolean get_applyPostStep(){
    	return applyPostStep.getValue();
    }
}

