import processing.core.*; 
import processing.xml.*; 

import processing.opengl.*; 
import processing.opengl.*; 

import java.applet.*; 
import java.awt.*; 
import java.awt.image.*; 
import java.awt.event.*; 
import java.io.*; 
import java.net.*; 
import java.text.*; 
import java.util.*; 
import java.util.zip.*; 
import java.util.regex.*; 

public class main extends PApplet {



//3d Friendly Game Engine Thingy.
//System.out.println("1");
//import java.awt.Button;
ArrayList gameState; //an array that will hold gamestates

long elapsedTime; //in miliseconds 
// Get current time
long start = 1;



public void setup() {   
 frameRate(60) ;
 //cursor(HAND);
  
  Frame framer;
   gameState = new ArrayList();
   
   gameState.add(new Play()); //play is a der. of GameState, and represents the actual game

   colorMode(RGB,100); //specifying colours is done between 0 and 100.
   rectMode(CENTER);   //mode for drawing rectangles
//   size(1024, 768, P3D); //screen size
  // size(800, 600, OPENGL);
   
   noStroke();
   fill(50);        //fill colour
   
   start =  System.currentTimeMillis();
 } 
 
 public void draw() {

   // Get elapsed time in milliseconds
    elapsedTime = System.currentTimeMillis()-start;
    start = System.currentTimeMillis();
    if (elapsedTime == 0)
      elapsedTime = 1;
  // System.out.println("framerate: " + 1000/elapsedTime + "  elapsedTime: " + elapsedTime);
    //System.out.println("elapsedTime " +System.currentTimeMillis());
   
   background(128,128,128); //background colour
  
   ((GameState)gameState.get(gameState.size()-1)).Update(elapsedTime);
   ((GameState)gameState.get(gameState.size()-1)).Draw(elapsedTime);


   CheckGameStateChange(); //check to see if the game state should be changed
                          //(ie. pop a state off and put it on top)
   
 }
 
boolean left=false;
boolean right=false;
boolean center=false;

public void mousePressed()
{
  ((GameState)gameState.get(gameState.size()-1)).mousePressed();
}

public void mouseReleased()
{
  ((GameState)gameState.get(gameState.size()-1)).mouseReleased();
} 

 public void CheckGameStateChange()
 {

 }
class Camera3D extends Entity
{
  float cwidth, cheight;
  
  PVector up, look;
  
  Camera3D()
  {
    cwidth = width; cheight = height;
    
    look = new PVector(0,0,0);
    up = new PVector(0,1,0);
    pos = new PVector(cwidth/2, cheight/2, 0);
  }
  
  public void Update()
  {
  }
  
  public void Draw()
  {
  }
  
}
class Enemy extends Entity
{
  int mode = 0; //mode = 0 is flying towards planet, mode = 1 is standing on planet.
  
  boolean dirLeft = true;

  PVector velocity = new PVector(0,0,0);
  float maxAirVel = 4;
  
  PVector down = new PVector(0,0,0);
  Planet onPlanet;

  boolean grounded = false;

  PImage img;

  float radius; //for starters, after basic system in, will implement rotation and quad coll.

  Quad2D originalQuadd;
  Quad2D quadd;
  

  Enemy(){}

  Enemy(PVector iPos, PVector iVel, PImage iImg)
  {
    img =iImg;
    radius = 43/2;      
    pos = iPos;
    quadd = new Quad2D(pos, 43, 43);
    originalQuadd = new Quad2D(pos, 43, 43);
    
    velocity.set(iVel);
    velocity.normalize();
    velocity.mult(maxAirVel);
  }

  //  void Move(int hor, int ver)
  //  {
  //    
  //    pos = new PVector(hor, ver, pos.z);
  //    quadd.Translate(new PVector(hor, ver, 0));
  //    swordQuad.Translate(new PVector(hor, ver, 0));
  //  }

  public void Update(long elapsedTime)
  {
    if (mode == 0)
    {
    Translate(velocity);
    }
    else if (mode == 1)
    {
    }
  }

  public void Translate(PVector iVelocity)
  {
    //iVelocity.mult(0.15);

    pos.add(iVelocity);

    //  quadd.MoveTo(pos);

    quadd.Translate(iVelocity);

  }

  public float FindAngle2D(PVector v1, PVector v2)
  {
    //float angle = -(180/PI)*atan2(v1.x*v2.y - v1.y*v2.x, v1.x*v2.x+v1.y*v2.y);
    float angle = -atan2(v1.x*v2.y - v1.y*v2.x, v1.x*v2.x+v1.y*v2.y);

    return angle;
  }

  public PVector ReturnNormal(PVector v1, boolean clockwise)
  {
    PVector normalV1 = new PVector();
    normalV1.set(v1);

    if (clockwise)
    {
      float y = normalV1.y;
      normalV1.y = -normalV1.x;
      normalV1.x = y;
    }
    else
    {
      float y = normalV1.y;
      normalV1.y = normalV1.x;
      normalV1.x = -y;
    }

    return normalV1;
  }

  public void MirrorPlayer()
  {
    quadd.MirrorQuad(down, pos);
   
    PVector temp0 = new PVector();
    temp0.set( originalQuadd.points[0]);
    PVector temp2 = new PVector();
    temp2.set( originalQuadd.points[2]);

    originalQuadd.points[0] = originalQuadd.points[1];
    originalQuadd.points[1] = temp0;
    originalQuadd.points[2] = originalQuadd.points[3];
    originalQuadd.points[3] = temp2;
  }

  
  public void SetEnemyAngle()
  {
    //set enemy
    float anglebetween = FindAngle2D( PVector.sub(down, pos), new PVector(0,1,0) );
    originalQuadd.MoveTo(pos);    
    quadd = originalQuadd.RotateQuad(pos,anglebetween);
  }
  

  public void Draw()
  {
    quadd.Draw(img);

  }

}



class EnemyManager extends Entity
{
  ArrayList enemiesList = new ArrayList();

  int spawnCounter = 0; //counts time

  float spawnTime = 10000; //time between each spawn in miliseconds
  float minSpawnTime = 2000; //minimum time between each spawn in miliseconds
  float spawnTimeMod = 0.95f; //0.94 each time an enemy spawns, the spawn time is multiplied by this number.

  float spawnDistance = 2000; //distance away from the play to spawn.
  PImage enemyImg = loadImage("mousemonster.png");
  EnemyManager(){
    
  }

  public void Update(long elapsedTime, PVector playerPos, PVector planetPos)
  {
    spawnCounter += elapsedTime;
    if (spawnCounter >= spawnTime)
    {
      spawnCounter = 0;
      spawnTime *= spawnTimeMod;
      if (spawnTime < minSpawnTime)
        spawnTime = minSpawnTime;

      float spawnAngle = random(360);
      spawnAngle /= 180 / PI;
      PVector spawnLocation = new PVector();
      spawnLocation = new PVector(playerPos.x, playerPos.y + spawnDistance, 0); 
      spawnLocation = RotateVector(playerPos, spawnAngle, spawnLocation);
      enemiesList.add(new Enemy(spawnLocation, PVector.sub(planetPos, spawnLocation), enemyImg));

    }


    for (int i = 0; i < enemiesList.size(); i++)
    {
      ((Enemy)enemiesList.get(i)).Update(elapsedTime);
    }   

  }

  public void Draw()
  {
    for (int i = 0; i < enemiesList.size(); i++)
    {
      ((Enemy)enemiesList.get(i)).Draw();
    } 

  }

  public PVector  RotateVector(PVector rotAxis, float rotAmount, PVector rotVec)
  {
    PVector rotateVec = new PVector();
    rotateVec.set(rotVec);
    rotateVec.sub(rotAxis);
    PVector returnVec = new PVector();

    returnVec.x = (rotateVec.x *(cos(rotAmount))) - (rotateVec.y *(sin(rotAmount)));
    returnVec.y = (rotateVec.x *(sin(rotAmount))) + (rotateVec.y *(cos(rotAmount)));
    returnVec.add(rotAxis);
    return returnVec;
  }

}




//import javax.media.opengl.*;
//import com.sun.opengl.util.*;

//
////add gui after i actually make the switch from p3d to OPENGL
String Mode = OPENGL;
String RMode = OPENGL;  // Rendering mode, mean do we use processing function to display or GL context functions
//String RMode = P3D;  // Rendering mode, mean do we use processing function to display or GL context functions
PFont fontA;
boolean bFlush = false;
//
class GUI
{
  Camera3D cameraa;
  Player p1;
  
  //stuff for displaying the different stats:
  boolean hasBeenOnGround = true; //used to determine whether or not the player has just jumped into the air.
  int inAirTimer = 0;   //used to display the longest amount of time the player has been in the air so far.
  int inAirCounter = 0; //used to count the amount of time in the air since last jump.
  float airDistanceTimer = 0; //displays longest distance in air so far
  float airDistanceCounter = 0; //counts the distance in air since last jump
  
// ____________________
GUI(Camera3D icamera, Player ip1) 
{
  cameraa = icamera;
  p1 = ip1;
  if ( RMode == OPENGL )
    Mode = OPENGL;
  size(800, 600, Mode);

  if ( Mode == OPENGL )
  {
    // enable VSync to avoid framerate problems ?
  //  PGraphicsOpenGL pgl;
   // GL gl;
   // pgl = (PGraphicsOpenGL)g; //processing graphics object
   // gl = pgl.beginGL(); //begin opengl
    //gl.setSwapInterval(1); //set vertical sync on, screen refresh is 60 Hz so framerate will be 60
   // pgl.endGL(); //end opengl
  }
  else
  {
    frameRate(60); // a little more than 60 because 60hz screen and vsync activated ?
  }

  background(10);
  fontA = loadFont("ComicSansMS-48.vlw");
  
  hasBeenOnGround = p1.grounded;
}

// ____________________
public void Draw(long elapsedTime) 
{
  //background(10);

  // display framerate
  int FillColor = color(60,20,100);
  fill(FillColor);
  textFont(fontA, 40);
  text("fps         "+ (int)frameRate,                  cameraa.pos.x - cameraa.cwidth + 35, cameraa.pos.y - cameraa.cheight + 70);
  text("Health      " + p1.life,     cameraa.pos.x - cameraa.cwidth + 35, cameraa.pos.y - cameraa.cheight + 105);
   text("Air Time                 " + ((float)inAirCounter / 1000),     cameraa.pos.x - cameraa.cwidth + 35, cameraa.pos.y + cameraa.cheight - 70);
   text("Max Air Time         " + ((float)inAirTimer / 1000),     cameraa.pos.x - cameraa.cwidth + 35, cameraa.pos.y + cameraa.cheight - 35);
  text("Air Distance           " + ((float)airDistanceCounter ),     cameraa.pos.x + cameraa.cwidth - 600, cameraa.pos.y + cameraa.cheight - 70);
   text("Max Air Distance  " + ((float)airDistanceTimer ),     cameraa.pos.x+ cameraa.cwidth -600, cameraa.pos.y + cameraa.cheight - 35);
  
  ProcessInAirTimer(elapsedTime);

  //text("Viewport Mode	  = " + Mode,     cameraa.pos.x - cameraa.cwidth + 15, cameraa.pos.y - cameraa.cheight + 70);
  //text("Instruction Mode (R) = " + RMode, cameraa.pos.x - cameraa.cwidth + 15, cameraa.pos.y - cameraa.cheight + 105);
  //text("force flush (F) ? " + bFlush,     cameraa.pos.x - cameraa.cwidth + 15, cameraa.pos.y - cameraa.cheight + 140);
  
  
}


public void  ProcessInAirTimer(long elapsedTime) 
 {
   if (hasBeenOnGround && !p1.grounded)
   {
     hasBeenOnGround = false;
     
   }
   else if (!hasBeenOnGround && !p1.grounded)
   {
     inAirCounter += elapsedTime;
     
      airDistanceCounter += p1.velocity.mag();
   }
   else if (!hasBeenOnGround && p1.grounded)
   {
     hasBeenOnGround = true;
     
     if (inAirCounter > inAirTimer)
       inAirTimer = inAirCounter;
       
    if (airDistanceCounter > airDistanceTimer)
       airDistanceTimer = airDistanceCounter;
       
     airDistanceCounter = 0;
     
     inAirCounter = 0;
   }
   
 }
 
// ____________________
//void Update()
//{
//  if (keyPressed)
//  {
//  if ( key == 'f' || key == 'F' )
//    bFlush = !bFlush;
//
//  if ( key == 'r' || key == 'R' )
//  {
//    if ( Mode == OPENGL )
//    {
//	if (RMode == OPENGL )
//	{
//	  RMode = P3D;
//	  println("going to P3D Rmode");
//	}
//	else if ( RMode == P3D )
//	{
//	  RMode = OPENGL;
//	  println("going to OPENGL Rmode");
//	}
//    }
//    else
//	  println("Can't switch to OpenGL instructions in P3D render mode");
//  }
//  }
//}
}
class GameState
{
  boolean isActive;
  
  GameState()
  {

  }

  public int TakeInput(long elapsedTime)
  {
    
    
    return -1;
  }
  
  public void Update(long elapsedTime)
  {

  }

  public void Draw(long elapsedTime)
  {
  }

public void mousePressed()
{
 
}

public void mouseReleased()
{
 
} 

}

class Planet extends Entity
{

  PImage img;
  PImage effectImg;
  float radius;  //planet has stronger or weaker gravity based on radius.
  float radiusOfEffect;
  Quad2D quadd;
  Quad2D effectQuadd;

  Planet() { quadd = new Quad2D(); effectQuadd = new Quad2D();}// img = loadImage("medmarbleplanet.png");  effectImg = loadImage("purple50.png");}
  Planet(PVector iPos, float iRadius, PImage iImg, PImage iEffectImg)
  {       
    radius = iRadius;
    
    radiusOfEffect = 250 + (radius*2);
    
    img = iImg;
    effectImg = iEffectImg;
    
    pos = iPos;
    quadd = new Quad2D(new PVector(pos.x, pos.y, 0), radius*2, radius*2);
    effectQuadd = new Quad2D(new PVector(pos.x, pos.y, 0), radiusOfEffect*2, radiusOfEffect*2);
    
    
  }

  public void Update()
  {
  }
  
  public void  RotatePlanet(PVector rotAxis, float rotAmount)
  {
     quadd.RotateQuad(rotAxis, rotAmount);
     
     pos = quadd.RotateVector(rotAxis, rotAmount, pos);
     
  }

  public void Draw()
  {
    //effectQuadd.Draw(effectImg);
    quadd.Draw(img);
  }

  public void set(Planet iPlanet)
  {
    pos.set(iPlanet.pos);
   radius = iPlanet.radius ;  //planet has stronger or weaker gravity based on radius.
  
   radiusOfEffect = iPlanet.radiusOfEffect;

   quadd.set(iPlanet.quadd);
      
   effectQuadd.set(iPlanet.effectQuadd);
  }
}

//import java.awt.AWTException; 
//import java.awt.Robot; 
//import java.awt.event.KeyEvent;
//import java.awt.Frame;

class Play extends GameState
{
  GUI gui;
  
  float mouseInputThreshold = 1500;
  PImage planetImg = loadImage("smallmarbleplanet.png");
   PImage medPlanetImg = loadImage("medmarbleplanet.png");
  PImage planetEffectImg = loadImage("purple50.png");
    
  int centerPosX, centerPosY; //where the mouse needs to reset to.
  boolean mouseLocked = true;
  Camera3D cameraa;
  Player p1; 
  ArrayList planetList;
  int inputCounter = -1;

  float maxMouseSpeed = 14;
  float farAwayPlanetaryPull = 4.25f; //strength of force when outside of all planets effect range

  EnemyManager enMan;
  
  Play()
  {        
    
    enMan = new EnemyManager();
    cameraa = new Camera3D();
    p1 = new Player(new PVector(0, 0-216 , 0), 32, 32);
    planetList = new ArrayList();
    planetList.add(new Planet(new PVector(0 - 100, 0 - 100 , 0), 100,planetImg, planetEffectImg));    
    planetList.add(new Planet(new PVector(width/2, height/2 +200, 0), 200,planetImg, planetEffectImg));
    planetList.add(new Planet(new PVector(width/2 + 850, height/2 +850 , 0), 500,medPlanetImg, planetEffectImg));
    planetList.add(new Planet(new PVector(width/2 + 1800, height/2 - 160 , 0), 350,medPlanetImg, planetEffectImg));
    planetList.add(new Planet(new PVector(width/2 + 1950, height/2 - 1250 , 0), 250,planetImg, planetEffectImg));
    planetList.add(new Planet(new PVector(width/2 + 1200, height/2 -630 , 0), 50,planetImg, planetEffectImg));
    planetList.add(new Planet(new PVector(width/2 -400, height/2 +1050 , 0), 300,medPlanetImg, planetEffectImg));    
    planetList.add(new Planet(new PVector(width/2 -1100, height/2 +800 , 0), 150,planetImg, planetEffectImg)); 
    planetList.add(new Planet(new PVector(width/2 -1400, height/2 +1000 , 0),100,planetImg, planetEffectImg));    
    
    gui = new GUI(cameraa, p1);
    //ResetMouse();

    CollisionPlayerOnPlanet(); //check and handle collisions as soon as play start
  }
  
int   counter = 0;
  public int TakeInput(long elapsedTime)
  {
    if(keyPressed)
    {
      //check to see if the player changes the game state (ie. pushes q to quit)
    }

    if(left == true && right == false) 
    {
      if (p1.grounded && p1.jumping == false)
      {
        counter++;
        System.out.println(counter);
        p1.jumping = true;
        p1.jumpCounter = 0;
        p1.grounded = false;
        PVector jumpV = PVector.sub(p1.pos, p1.onPlanet.pos);
        jumpV.normalize();
        p1.velocity.add( PVector.mult(  jumpV, p1.jumpPower  )   );

      }
      else if (p1.jumping == true)
      {

        if (p1.jumpCounter < p1.jumpMax)
        {

          p1.jumpCounter+= elapsedTime;
          PVector jumpV = PVector.sub(p1.pos, p1.onPlanet.pos);
          jumpV.normalize();
          p1.velocity.add(PVector.mult(  PVector.mult(  jumpV,  (float)(p1.jumpMax - p1.jumpCounter) * elapsedTime  )  , p1.jumpBoost ));
        }
        else
        {
          //p1.jumping = false; 
          //p1.jumpCounter = 0;
        }
      }             
    } 
    else
    {


      p1.jumping = false;
      p1.jumpCounter = 0;
    }

    //swing the sword.
    if(right == true) 
    {
      if (!p1.grounded )
        p1.currentAttackTimeInAir += elapsedTime;
        
        p1.currentAttackTime += elapsedTime;
      
    //  if (p1.currentAttackTime <= p1.attackTime)
    //  {
      
        //continue the previous input for a given amount of time.
        p1.mouseInput.set(p1.prevMouseInput);
        
    //  }
     // else
    //  {
     //   p1.mouseInput = new PVector (0,0,0);
    //  }
      
      //swing the sword:
      if (p1.currentAttackTime <= p1.swingTime)
      {
        p1.SetSwordAngle();
          if (p1.dirLeft == true)
          {
             p1.swordQuad =  p1.swordQuad.RotateQuad(p1.pos, PI *  ((float)p1.currentAttackTime/p1.swingTime)   );
             
             p1.swordCollPos1 = p1.RotateVector(p1.pos, PI *  ((float)p1.currentAttackTime/p1.swingTime), p1.swordCollPos1);
             p1.swordCollPos2 = p1.RotateVector(p1.pos, PI *  ((float)p1.currentAttackTime/p1.swingTime), p1.swordCollPos2); 
          }
          else
          {
            p1.swordQuad =  p1.swordQuad.RotateQuad(p1.pos, -PI *  ((float)p1.currentAttackTime/p1.swingTime)   );
            p1.swordCollPos1 = p1.RotateVector(p1.pos, -PI *  ((float)p1.currentAttackTime/p1.swingTime), p1.swordCollPos1);
            p1.swordCollPos2 = p1.RotateVector(p1.pos, -PI *  ((float)p1.currentAttackTime/p1.swingTime), p1.swordCollPos2); 
             
          }
      }
      else if (p1.currentAttackTime > p1.swingTime) //if the sword has been swung, hold it out in front of the player
      {
             p1.SetSwordAngle();
          if (p1.dirLeft == true)
          {
             p1.swordQuad =  p1.swordQuad.RotateQuad(p1.pos, PI   );
             p1.swordCollPos1 = p1.RotateVector(p1.pos, PI, p1.swordCollPos1);
             p1.swordCollPos2 = p1.RotateVector(p1.pos, PI, p1.swordCollPos2); 
          }
          else
          {
            p1.swordQuad =  p1.swordQuad.RotateQuad(p1.pos, -PI    );
            p1.swordCollPos1 = p1.RotateVector(p1.pos, -PI, p1.swordCollPos1);
            p1.swordCollPos2 = p1.RotateVector(p1.pos, -PI, p1.swordCollPos2); 
          }
      }
      
      
        p1.attacking = true;
        
        CollisionSwordOnEnemies();
    }
    else
    {
      p1.SetSwordAngle();
      p1.attacking = false;
      p1.currentAttackTime = 0;
      p1.currentAttackTimeInAir = 0;
      
      p1.mouseInput = new PVector((((float)mouseX + 3) - (float)width/2) * elapsedTime, (((float)mouseY + 25) - (float)height/2) * elapsedTime , 0); //the +3 and + 25 are something fucked, but it makes it work
           
       
       if (p1.mouseInput.mag() < mouseInputThreshold)
       {
         p1.mouseInput = new PVector (0,0,0);
       }
       else
       {
         float oldMagnitude = p1.mouseInput.mag();
         p1.mouseInput.normalize();
         p1.mouseInput.mult( oldMagnitude - mouseInputThreshold);
       }
     p1.prevMouseInput.set(p1.mouseInput);
   //  System.out.println("elapsedTime " + elapsedTime + " Mouse X Y = " + mouseX + " " + mouseY);
    // PrintVector(p1.mouseInput); 
    }

    return -1;
  }
  
  public void HandleMoveOnGround(long elapsedTime)
  {
    float mSpeed = p1.mouseInput.mag();
      PVector mInput = new PVector();
      mInput.set( p1.mouseInput ); //set to mouseInput because we only take input every other frame, and operating
      //directly on p1.mouseInput will fuck it all up.
      mInput.normalize();

      mInput.mult((float)elapsedTime/ ((width)/mSpeed)); //TODO: this is terrible.

      if (mInput.mag() > maxMouseSpeed)
      {
        mInput.normalize();
        mInput.mult(maxMouseSpeed);
      }
      
      if (right == true)
      {
       // float timeFactor = 1 - (( (float)p1.currentAttackTime / (float)p1.attackTime) );
        float timeFactor = 1 - (( ((float)p1.currentAttackTime - p1.currentAttackTimeInAir ) 
                               / (float)p1.attackTime) );
        
        if (timeFactor <0)
          timeFactor = 0;
          
        mInput.mult(timeFactor);
        // mInput.mult(0.01);
      }
     
      
      PVector move = Orbit(p1.pos, mInput, p1.onPlanet.pos);// PVector.sub( (PVector.add(p1.pos,mInput)), p1.onPlanet.pos);
      

      p1.velocity = (move);
  }

  public void Update(long elapsedTime)
  { 
    
    //only update input 30 times per second:
    inputCounter++;
    if (inputCounter > 0)
    {  
      int stateID = TakeInput(elapsedTime); //if stateID != -1, then change to the correct state.
      inputCounter = 0;  
    }
   // if (mouseLocked)
    //  ResetMouse();


    PVector planetaryForce = new PVector(0,0,0);

    //IF ON A PLANET
    if (p1.grounded)  
    {      
      
       HandleMoveOnGround(elapsedTime);


    }
    //ELSE IF IN AIR
    else 
    {
      //Loop through planets and apply force to player according to distance from planet and size of planet.
      for (int i = 0; i < planetList.size(); i++)
      {
        if ( (CircleCollision(p1.pos, /*p1.radius*/ 0.01f, ((Planet)planetList.get(i)).pos, ((Planet)planetList.get(i)).radiusOfEffect)).mag() > 0 )
        {

          PVector force = new PVector(((Planet)planetList.get(i)).pos.x - p1.pos.x, 
          ((Planet)planetList.get(i)).pos.y - p1.pos.y, 0);

          float magnitude = force.mag();
          
          
          force.normalize();

          float forceFactor = (magnitude - ((Planet)planetList.get(i)).radius ) /  
          (((Planet)planetList.get(i)).radiusOfEffect - ((Planet)planetList.get(i)).radius);

          forceFactor = 1 - forceFactor;
          
          forceFactor *= elapsedTime;
              
          force.mult( forceFactor );
        
          planetaryForce.add(force);

        }
      }  



      if (planetaryForce.mag() >0) 
      {
        p1.down.set(  planetaryForce );
      }
      else //if outside of all planets' gravity, make the player fall slowly to the closest planet.
      {
        Planet closestPlanet = ((Planet)planetList.get(0));

        float distToClosestPlanet = (PVector.sub(((Planet)planetList.get(0)).pos, p1.pos)).mag() - ((Planet)planetList.get(0)).radius;
        p1.down.set(PVector.sub(closestPlanet.pos, p1.pos));

        if (planetList.size() > 1)
        {
          for (int i = 1; i < planetList.size(); i++)
          {
            float distToPlanet = (PVector.sub(((Planet)planetList.get(i)).pos, p1.pos)).mag() - ((Planet)planetList.get(i)).radius;
            if (distToPlanet < distToClosestPlanet)
            {
              distToClosestPlanet = distToPlanet;

              closestPlanet = (Planet)planetList.get(i);
              p1.down.set(PVector.sub(closestPlanet.pos, p1.pos));   

            }
          }
        }

        planetaryForce = new PVector(closestPlanet.pos.x - p1.pos.x, 
        closestPlanet.pos.y - p1.pos.y, 0);
        planetaryForce.normalize();
        planetaryForce.mult(farAwayPlanetaryPull);
        
      }

      p1.down.normalize();
      p1.down.mult(50);
      p1.down.add(p1.pos);
      //p1.downQuad.MoveTo(p1.down);


      //TAKE MOUSE INPUT AND MOVE THE PLAYER
      float mSpeed = p1.mouseInput.mag();
      PVector mInput = new PVector();
      mInput.set( p1.mouseInput ); //set to mouseInput because we only take input every other frame, and operating
      //directly on p1.mouseInput will fuck it all up.
      mInput.normalize();

      mInput.mult((float)elapsedTime /32); //TODO: this is terrible.

      if (mInput.mag() > (maxMouseSpeed *p1.inAirMouseInputMod))
      {
        mInput.normalize();
        mInput.mult((maxMouseSpeed *p1.inAirMouseInputMod));
      }

      PVector move = Orbit(p1.pos, mInput, p1.down);// PVector.sub( (PVector.add(p1.pos,mInput)), p1.onPlanet.pos);
     
      p1.velocity.add(move);

      planetaryForce.div(12);
      
      p1.velocity.add(planetaryForce);
      


    }

    //air resistance:
    p1.velocity.mult(1 - ((float)elapsedTime / (float)1000));
    
    
    //p1.velocity.mult(0.99);
    //translate the player based on his calculated velocity
    p1.Translate(p1.velocity);  


    if (p1.grounded)
    {
       if (p1.FindAngle2D(p1.velocity, PVector.sub(p1.onPlanet.pos, p1.pos)) > 0)
      {
        p1.SetPlayerDir(false);
      }
      else if (p1.FindAngle2D(p1.velocity, PVector.sub(p1.onPlanet.pos, p1.pos)) < 0)
      {
        p1.SetPlayerDir(true);
      }
    }
        p1.SetPlayerAngle();
    //Translate camera;
    cameraa.pos.add( PVector.mult( PVector.sub(p1.pos, cameraa.pos), 0.05f ) ); //0.05

    
    
    enMan.Update(elapsedTime, p1.pos, ((Planet)planetList.get( (int)random(planetList.size()-1) )).pos );
    
     CollisionPlayerOnPlanet();
     CollisionEnemiesOnPlanet();
     CollisionPlayerOnEnemy();
     
  }

  public void CollisionSwordOnEnemies()
  {
    for (int i = enMan.enemiesList.size()-1; i >= 0; i--)
    {
      //if the enemy is generally close..
      if ( CircleCollisionNoOverlap(p1.swordQuad.center, p1.swordImg.width*2, 
          ((Enemy)enMan.enemiesList.get(i)).pos, ((Enemy)enMan.enemiesList.get(i)).radius) ) 
          {
            //if the enemy is colliding with the sword:
          if ( CircleCollisionNoOverlap(p1.swordCollPos1, p1.swordCollRadius1, 
          ((Enemy)enMan.enemiesList.get(i)).pos, ((Enemy)enMan.enemiesList.get(i)).radius) ) 
          {
            enMan.enemiesList.remove(i);
            continue;
          }
          
          if ( CircleCollisionNoOverlap(p1.swordCollPos2, p1.swordCollRadius2, 
          ((Enemy)enMan.enemiesList.get(i)).pos, ((Enemy)enMan.enemiesList.get(i)).radius) ) 
          {     
            enMan.enemiesList.remove(i);
            continue; 
          } 
            
            
          }
    }
    
  }
  public void CollisionEnemiesOnPlanet()
  {
    Planet closestPlanet = new Planet();

    
    if (enMan.enemiesList.size() > 0)
    {
        for (int i = 0; i < enMan.enemiesList.size(); i++)
        {
          //if the enemy is in flight mode...
          if (((Enemy)enMan.enemiesList.get(i)).mode ==0)
          {
          closestPlanet.set( (Planet)planetList.get(0) ) ; 
          for  (int j = 0; j < planetList.size(); j++)
          {
           // if a planet is closer, set that to be the closest Planet.
              if (  ((PVector.sub(closestPlanet.pos, ((Enemy)enMan.enemiesList.get(i)).pos)).mag() - closestPlanet.radius)  > 
                   ((PVector.sub(((Planet)planetList.get(j)).pos, ((Enemy)enMan.enemiesList.get(i)).pos)).mag() - ((Planet)planetList.get(j)).radius) )
                {
                  closestPlanet.set( ((Planet)planetList.get(j)) );
                }
          }
          //now do a bounding circle check on the closest planet to see if it has landed on it yet.
          PVector overlap = CircleCollision( ((Enemy)enMan.enemiesList.get(i)).pos,((Enemy)enMan.enemiesList.get(i)).radius,
                                              closestPlanet.pos, closestPlanet.radius);
          if (overlap.mag() != 0)
          {
            ((Enemy)enMan.enemiesList.get(i)).Translate(PVector.mult(overlap,0.99f));
            ((Enemy)enMan.enemiesList.get(i)).mode = 1;
          }
          }
          
        }  
    }

    
  }
  
  public void CollisionPlayerOnEnemy() //death case
  {
     if (enMan.enemiesList.size() > 0)
    {
        for (int i = 0; i < enMan.enemiesList.size(); i++)
        {
          if (CircleCollisionNoOverlap(p1.pos, p1.radius, ((Enemy)enMan.enemiesList.get(i)).pos, ((Enemy)enMan.enemiesList.get(i)).radius))
          {
            p1.life -= 1;
             // p1.Translate(PVector.add(PVector.mult(p1.pos, -1), new PVector( ((Planet)planetList.get(0)).pos.x+((Planet)planetList.get(0)).radius  , ((Planet)planetList.get(0)).pos.y+((Planet)planetList.get(0)).radius, 0)));
             // p1.velocity = new PVector(); //Vector.sub( p1.pos;
             // p1.grounded = false;
             // cameraa.pos.add( PVector.mult(PVector.sub(p1.pos, cameraa.pos), 0.5)   );
          }
        }
    }
  }
  
  public void CollisionPlayerOnPlanet()
  {

    if (p1.grounded == false)
    {

      for (int i = 0; i < planetList.size(); i++)
      {


        PVector overlap = CircleCollision(p1.pos, p1.radius, ((Planet)planetList.get(i)).pos, ((Planet)planetList.get(i)).radius);

        if (overlap.mag() > 0)
        {
          p1.Translate(PVector.mult(overlap,0.99f));

         // p1.jumping = false;
          p1.jumpCounter = 0;

          p1.grounded = true;     

          p1.onPlanet = (Planet)planetList.get(i);
          p1.down.set( p1.onPlanet.pos);

         // p1.downQuad.MoveTo(p1.down);

        }
      }
    }
  }

  public boolean CircleCollisionNoOverlap(PVector aPos, float aRadius, PVector bPos, float bRadius)
  {
    if ((PVector.sub(aPos, bPos)).mag() < (aRadius + bRadius))
      return true;
    else
      return false;
  }
  
  public PVector CircleCollision(PVector aPos, float aRadius, PVector bPos, float bRadius)
  {
    if ((PVector.sub(aPos, bPos)).mag() < (aRadius + bRadius))
    {
      PVector distance = (PVector.sub(aPos, bPos));

      float overlap = (aRadius + bRadius) - distance.mag();

      distance.normalize();
      if (distance.mag() ==0)
        distance.x = 0.1f;

      return PVector.mult(distance, overlap);

    }
    else
      return new PVector(0,0,0);
  }



  //Function that takes in a velocity vector, and returns a new dir vector to orbit around the vector planetPos.
  public PVector Orbit(PVector pos, PVector dir, PVector planetPos)
  {
    PVector newVector = PVector.add(pos, dir);

    newVector.sub(planetPos);

    newVector.normalize();

    newVector.mult( (PVector.sub(pos, planetPos)).mag() );

    newVector.add(planetPos);

    newVector.sub(pos);

    return newVector;

  }


//  void ResetMouse()
//  {
//    centerPosX = (int)(frame.getLocation().getX() + width/2);
//    centerPosY = (int)(frame.getLocation().getY() + height/2);
//
//    try { 
//
//      Robot robot = new Robot(); 
//      // Creates the delay of 5 sec so that you can open notepad before 
//      // Robot start writting 
//
//      robot.mouseMove(centerPosX,centerPosY);
//
//    } 
//    catch (AWTException e) { 
//      e.printStackTrace(); 
//    }
//  }

  public void Draw(long elapsedTime)
  {    
    //noCursor(); //hides the cursor

      background(0); //background colour

    ortho( - cameraa.cwidth , 
    cameraa.cwidth , 
    - cameraa.cheight, 
    cameraa.cheight , -100, 100);

    camera(cameraa.pos.x, cameraa.pos.y, 10, // eyeX, eyeY, eyeZ
    cameraa.pos.x, cameraa.pos.y, 0.0f, // lookX, lookY, lookZ
    0.0f, 1.0f, 0.0f); // upX, upY, upZ


    for (int i = 0; i < planetList.size(); i++)
    {
      ((Planet)planetList.get(i)).Draw();
    }
    p1.Draw();

    enMan.Draw();
    
    gui.Draw(elapsedTime); 
  }


  public void PrintVector(PVector vec)
  {
    System.out.println("vec x y z: " + vec.x + " " + vec.y + " " + vec.z  );
  }
  



boolean left=false;
boolean right=false;
boolean center=false;

public void mousePressed()
{
  if(mouseButton==LEFT)
    left=true;
  if(mouseButton==RIGHT)
    right=true;
  if(mouseButton==CENTER)
    center=true;
}

public void mouseReleased()
{
  if(mouseButton==LEFT)
    left=false;
  if(mouseButton==RIGHT)
    right=false;
  if(mouseButton==CENTER)
    center=false;
} 


}













class Player extends Entity
{
  int life = 100;
  boolean dirLeft = true;

  PVector velocity = new PVector(0,0,0);


  PVector mouseInput = new PVector(0,0,0); //used to store the current mouse input
  PVector prevMouseInput = new PVector(0,0,0);

  PVector down = new PVector(0,0,0);
  Planet onPlanet;

  boolean grounded = false;

  PImage img;

  float radius; //for starters, after basic system in, will implement rotation and quad coll.

  Quad2D originalQuadd;
  Quad2D quadd;

//  Quad2D downQuad;
//  PImage downImg;
  Quad2D swordQuad;
  PImage swordImg;
  
  //stuff for attacking:
  int currentAttackTime = 0;
  int currentAttackTimeInAir = 0; //so that when the player lands, they still do a slide even if they have been in the air for a long time.
  int attackTime = 1000;
  boolean attacking = false;
  int swingTime = 140; //milliseconds
  PVector swordCollPos1;
  float swordCollRadius1 = 15;
  PVector swordCollPos2;
  float swordCollRadius2 = 15;
  
  //stuff for jumping:
  boolean jumping = false;
  int jumpCounter = 0;
  int jumpMax = 255; //15
  float jumpPower = 11.5f; //initial jump power
  float jumpBoost = 0.00043f; //sustained jump multiplier
  
  float  inAirMouseInputMod = 0.03f;
  
  Player()
  {
    //    img = loadImage("warrior1.png");
    //   // quad = new Quad2D();
    //    pos = new PVector(width/2 , height/2 +1);
    //    quadd = new Quad2D(new PVector(width/2, height/2 +1), 32, 32);
    //    originalQuadd = new Quad2D(new PVector(width/2, height/2 +1), 32, 32);
    //    
    //    downQuad = new Quad2D(new PVector(width/2, height/2 +1), 8, 8);
    //    downImg = loadImage("biggreencircle.png");
    //    
    //    swordQuad = new Quad2D(new PVector(pos.x, pos.y), 8, 8);
    //    swordImg = loadImage("sword1.png");
    //    
    //    radius = 16;
  }

  Player(PVector iPos, float iWidth, float iHeight)
  {
    img = loadImage("warrior1.png");
    radius = iHeight/2;      
    pos = iPos;
    quadd = new Quad2D(pos, iWidth*1.2f, iHeight*1.2f);
    originalQuadd = new Quad2D(pos, iWidth*1.2f, iHeight*1.2f);

//    downQuad = new Quad2D(new PVector(width/2, height/2 +5), 16, 16);
//    downImg = loadImage("biggreencircle.png");


    swordImg = loadImage("sword1.png");
    swordQuad = new Quad2D (new PVector(pos.x - swordImg.width + swordImg.width*0.35f, pos.y), 
                             swordImg.width, swordImg.height);
                             
     swordCollPos1 = new PVector(pos.x - swordImg.width + swordImg.width*0.025f, pos.y);
     swordCollPos2 = new PVector(pos.x - swordImg.width + swordImg.width*0.37f, pos.y);
     

    
  }

  //  void Move(int hor, int ver)
  //  {
  //    
  //    pos = new PVector(hor, ver, pos.z);
  //    quadd.Translate(new PVector(hor, ver, 0));
  //    swordQuad.Translate(new PVector(hor, ver, 0));
  //  }

  public void Update()
  {
  }

  public void Translate(PVector iVelocity)
  {
    //iVelocity.mult(0.15);

    pos.add(iVelocity);
    swordCollPos1.add(iVelocity);
    swordCollPos2.add(iVelocity);
    //  quadd.MoveTo(pos);

    quadd.Translate(iVelocity);

    swordQuad.Translate(iVelocity);
  }

  public float FindAngle2D(PVector v1, PVector v2)
  {
    //float angle = -(180/PI)*atan2(v1.x*v2.y - v1.y*v2.x, v1.x*v2.x+v1.y*v2.y);
    float angle = -atan2(v1.x*v2.y - v1.y*v2.x, v1.x*v2.x+v1.y*v2.y);

    return angle;
  }

  public PVector ReturnNormal(PVector v1, boolean clockwise)
  {
    PVector normalV1 = new PVector();
    normalV1.set(v1);

    if (clockwise)
    {
      float y = normalV1.y;
      normalV1.y = -normalV1.x;
      normalV1.x = y;
    }
    else
    {
      float y = normalV1.y;
      normalV1.y = normalV1.x;
      normalV1.x = -y;
    }

    return normalV1;
  }

  public void MirrorPlayer()
  {
    quadd.MirrorQuad(down, pos);
    //originalQuadd.MirrorQuad(down, pos);
    swordQuad.MirrorQuad(down, pos);
    swordCollPos1 = MirrorVector2D( swordCollPos1, down, pos);
    swordCollPos2 = MirrorVector2D( swordCollPos2, down, pos);
    
    
    PVector temp0 = new PVector();
    temp0.set( originalQuadd.points[0]);
    PVector temp2 = new PVector();
    temp2.set( originalQuadd.points[2]);

    originalQuadd.points[0] = originalQuadd.points[1];
    originalQuadd.points[1] = temp0;
    originalQuadd.points[2] = originalQuadd.points[3];
    originalQuadd.points[3] = temp2;
  }

  public void SetPlayerDir(boolean left)
  {
    if (left != dirLeft)
    {
      //mirror the geometry along the axis between p1.pos and the planets center.
      MirrorPlayer();

      dirLeft = left;
    }
  }
  
  public void SetPlayerAngle()
  {
    //set player
    float anglebetween = FindAngle2D( PVector.sub(down, pos), new PVector(0,1,0) );
    originalQuadd.MoveTo(pos);    
    quadd = originalQuadd.RotateQuad(pos,anglebetween);
    
    if (attacking == false)
    {
      SetSwordAngle();
    }
  }
  
  public void SetSwordAngle()
  {
    //set sword:
    
    float swordAngle = FindAngle2D( PVector.sub(swordQuad.center, pos), PVector.sub( down, pos));
    
    float newSwordAngle = 0;
    
    if (dirLeft == true) //see how it should rotate
      newSwordAngle = FindAngle2D( ReturnNormal(PVector.sub( down, pos), false), PVector.sub( down, pos));
    else
      newSwordAngle = FindAngle2D( ReturnNormal(PVector.sub( down, pos), true), PVector.sub( down, pos));  
      
    newSwordAngle -= swordAngle;
    swordQuad = swordQuad.RotateQuad(pos, newSwordAngle); //rotate the sword
    swordCollPos1 = RotateVector(pos, newSwordAngle, swordCollPos1);
    swordCollPos2 = RotateVector(pos, newSwordAngle, swordCollPos2); 
    
  }
  
   public PVector  RotateVector(PVector rotAxis, float rotAmount, PVector rotVec)
  {
    PVector rotateVec = new PVector();
    rotateVec.set(rotVec);
    rotateVec.sub(rotAxis);
    PVector returnVec = new PVector();
    
    returnVec.x = (rotateVec.x *(cos(rotAmount))) - (rotateVec.y *(sin(rotAmount)));
    returnVec.y = (rotateVec.x *(sin(rotAmount))) + (rotateVec.y *(cos(rotAmount)));
    returnVec.add(rotAxis);
    return returnVec;
  }
  
   public PVector MirrorVector2D(PVector v1, PVector axisA, PVector axisB)
  {
    PVector axis = PVector.sub(axisA, axisB);
    
    PVector axisNormal = ReturnNormal(axis, true);
    
    PVector mirrorVec = new PVector();
    
    mirrorVec.set(v1);
    mirrorVec.sub(axisB);
    mirrorVec = PVector.sub(mirrorVec, PVector.div( PVector.mult( axisNormal, 2*mirrorVec.dot(axisNormal) ), pow(axisNormal.mag(),2))     );
    mirrorVec.add(axisB);
      
     return mirrorVec;
  }
  
  public void Draw()
  {
    quadd.Draw(img);

    //downQuad.MoveTo(swordCollPos2);
   // downQuad.Draw(downImg);

    swordQuad.Draw(swordImg);
  }

  
}



class Quad2D
{

  PVector[] points = new PVector[4];
  
  PVector center;
  
  Quad2D()
  {
     points[0] = new PVector(0,0,0);
     points[1] = new PVector(100,0,0);
     points[2] = new PVector(100,100,0);
     points[3] = new PVector(0,100,0);
     
      SetCenter();
  }
  
  Quad2D(PVector p1, PVector p2, PVector p3, PVector p4)
  {
     points[0] = p1;
     points[1] = p2;
     points[2] = p3;
     points[3] = p4;    
     
     SetCenter();
  }
  
  Quad2D(PVector iPos, float iWidth, float iHeight)
  {
     points[0] = new PVector(iPos.x - iWidth/2, iPos.y - iHeight/2, iPos.z );
     points[1] = new PVector(iPos.x + iWidth/2, iPos.y - iHeight/2, iPos.z );
     points[2] = new PVector(iPos.x + iWidth/2, iPos.y + iHeight/2, iPos.z );
     points[3] = new PVector(iPos.x - iWidth/2, iPos.y + iHeight/2, iPos.z );
     
     center = new PVector();
     center.set(iPos);
  }
  
  public void set(Quad2D inQuad)
  {
    points[0].set(inQuad.points[0]);
    points[1].set(inQuad.points[1]);
    points[2].set(inQuad.points[2]);
    points[3].set(inQuad.points[3]);
    center.set(inQuad.center);
  }
  
  public void SetCenter() //positions the center at the average of all points
  {
    PVector avg = new PVector(0,0,0);
    
    for (int i = 0; i < 4; i++)
            avg.add(points[i]);
            
    avg.div(4);
    
    center = avg;
  }
  
  public void Translate(PVector change)
  {
     points[0].add(change);
     points[1].add(change);
     points[2].add(change);
     points[3].add(change);
     
     center.add(change);
  }
  
  public void MoveTo(PVector dest)
  {
    PVector change = PVector.sub(dest, center);
    
    Translate(change);
    
//    float iWidth = points[1].x - points[0].x;
//    float iHeight = points[2].y - points[1].y;
//    
//    points[0] = new PVector(dest.x - iWidth/2, dest.y - iHeight/2, dest.z );
//    points[1] = new PVector(dest.x + iWidth/2, dest.y - iHeight/2, dest.z );
//    points[2] = new PVector(dest.x + iWidth/2, dest.y + iHeight/2, dest.z );
//    points[3] = new PVector(dest.x - iWidth/2, dest.y + iHeight/2, dest.z );
//     
//    center.set(dest);
  }
  
  public void Draw(PImage img)
  {
    beginShape(QUADS);
    texture(img);
    
    vertex(points[0].x, points[0].y, points[0].z, 0, 0);
    vertex(points[1].x, points[1].y, points[1].z, img.width, 0);
    vertex(points[2].x, points[2].y, points[2].z, img.width, img.height);
    vertex(points[3].x, points[3].y, points[3].z, 0, img.height);
    
    endShape();
  }
  
  public Quad2D RotateQuad(PVector rotAxis, float rotAmount)
  {
    Quad2D returnQuad = new Quad2D();
    returnQuad.points[0] = RotateVector(rotAxis, rotAmount, points[0]);
    returnQuad.points[1] = RotateVector(rotAxis, rotAmount, points[1]);
    returnQuad.points[2] = RotateVector(rotAxis, rotAmount, points[2]);
    returnQuad.points[3] = RotateVector(rotAxis, rotAmount, points[3]);
    
    returnQuad.center = RotateVector(rotAxis, rotAmount, center);
    
    return returnQuad;
  }
  
  public PVector  RotateVector(PVector rotAxis, float rotAmount, PVector rotVec)
  {
    PVector rotateVec = new PVector();
    rotateVec.set(rotVec);
    rotateVec.sub(rotAxis);
    PVector returnVec = new PVector();
    
    returnVec.x = (rotateVec.x *(cos(rotAmount))) - (rotateVec.y *(sin(rotAmount)));
    returnVec.y = (rotateVec.x *(sin(rotAmount))) + (rotateVec.y *(cos(rotAmount)));
    returnVec.add(rotAxis);
    return returnVec;
  }
  
  public void MirrorQuad(PVector axisA, PVector axisB)
  {
    points[0] = MirrorVector2D( points[0], axisA, axisB );
    points[1] = MirrorVector2D( points[1], axisA, axisB );
    points[2] = MirrorVector2D( points[2], axisA, axisB );
    points[3] = MirrorVector2D( points[3], axisA, axisB );
    
    center = MirrorVector2D( center, axisA, axisB );
  }
  
   public PVector MirrorVector2D(PVector v1, PVector axisA, PVector axisB)
  {
    PVector axis = PVector.sub(axisA, axisB);
    
    PVector axisNormal = ReturnNormal(axis, true);
    
    PVector mirrorVec = new PVector();
    
    mirrorVec.set(v1);
    mirrorVec.sub(axisB);
    mirrorVec = PVector.sub(mirrorVec, PVector.div( PVector.mult( axisNormal, 2*mirrorVec.dot(axisNormal) ), pow(axisNormal.mag(),2))     );
    mirrorVec.add(axisB);
      
     return mirrorVec;
  }
  
  public PVector ReturnNormal(PVector v1, boolean clockwise)
  {
    PVector normalV1 = new PVector();
    normalV1.set(v1);

    if (clockwise)
    {
      float y = normalV1.y;
      normalV1.y = -normalV1.x;
      normalV1.x = y;
    }
    else
    {
      float y = normalV1.y;
      normalV1.y = normalV1.x;
      normalV1.x = -y;
    }

    return normalV1;
  }

}
class Entity
{
  Entity parent = null;
  
  PVector pos;
  int layer = 0;
  Entity()
  {
    pos = new PVector(0,0,0-layer);
  }
  
  public void Update()
  {
  }
  
  public void Draw()
  {
  }
  
}

  static public void main(String args[]) {
    PApplet.main(new String[] { "--bgcolor=#F0F0F0", "main" });
  }
}
