// ---------------------------------------------------------
// Class: Vehicle
// ---------------------------------------------------------
// An abstract class that describes a braitenberg vehicle,
// with two sensors and two wheels. Must be subclassed by
// specific types of vehicles that implement movement logic 
// (i.e. how the sensors are connected to the wheels). Knows
// how to draw itself and the sensors and wheels.
// ---------------------------------------------------------

import java.lang.*;
import java.util.*;
import processing.*;
import processing.core.*;

abstract class Vehicle {

  // some useful constants
  static final float BODY = 3.0f;
  static final float NOSE = 2.5f;
  static final float MOVEVEL = (float)Math.PI / 6;

  float x, y; // vehicle position
  float axle, halfaxle, axlesq; // axle dimensions
  float angle; // direction of vehicle
  float sangL, sangR; // sensor angles relative to vehicle angle
  float wangL, wangR; // wheel angles relative to vehicle angle
  PImage theimage;
  float wheeldiff, wheelavg; // difference and average wheel velocities

  Wheel wL, wR; // two wheels, left and right
  Sensor sL, sR; // two sensors, left and right

  float speedmod=1;
  // Constructor
  // -----------------------------
  Vehicle(PApplet pa, float x, float y, float angle, float axle, PImage theimage) {
    this.x = x;
    this.y = y;
    this.angle = angle;
    this.axle = axle;
    halfaxle = axle / 2;
    axlesq = axle * axle;
    this.theimage=theimage;
    // left and right wheel angles relative to angle of vehicle
    wangL = -pa.HALF_PI;
    wangR = pa.HALF_PI;

    // create the left and right wheels
    wL = new Wheel(x+halfaxle*pa.cos(angle+wangL), y+halfaxle*pa.sin(angle+wangL), 10, 0);
    wR = new Wheel(x+halfaxle*pa.cos(angle+wangR), y+halfaxle*pa.sin(angle+wangR), 10, 0);

    // left and right sensor angles relative to angle of vehicle
    sangL = - pa.HALF_PI/2;
    sangR = pa.HALF_PI/2;

    // create the left and right sensors
    sL = new Sensor(x+axle*pa.cos(angle+sangL), y+axle*pa.sin(angle+sangL));
    sR = new Sensor(x+axle*pa.cos(angle+sangR), y+axle*pa.sin(angle+sangR));    
  }

  // Draw Me!
  // -----------------------------
  void drawMe(PApplet pa) {




    pa.tint(255,255);
    // draw the wheels
    pa.fill(102,102,110);
    wL.drawMe(pa,angle,1, axle);
    wR.drawMe(pa,angle-pa.PI,-1, axle);

    // draw the nose
    //    pa.fill(255,0,0);
    //    pa.triangle(wL.x, wL.y, wR.x, wR.y, x+axle*pa.cos(angle)*NOSE, y+axle*pa.sin(angle)*NOSE);
    //
    //    // draw the body
    //    pa.fill(102,102,50);
    //    pa.ellipse(x, y, axle*BODY, axle*BODY);
    //
    //    // draw the sensors
    //    sL.drawMe(pa);
    //    sR.drawMe(pa);

    //Skin the beast!
    pa.imageMode(pa.CENTER);
    pa.pushMatrix();
    pa.translate(x,y);
    pa.rotate(angle+pa.PI/2);

    pa.image(theimage, 0,0);
    pa.imageMode(pa.CORNER);
    pa.popMatrix();
    //  pa.imageMode(CENTER);
    pa.tint(255,80);
  }

  // Abstract method to implement the vehicle logic
  // Base classes will need to define how sensors are connected to the wheels
  // -----------------------------
  abstract void doSenseLogic(SensoryField sfield, SensoryField ffield);
  abstract void doTortLogic(SensoryField tfield, SensoryField sfield);
  abstract float getSpeedRat();
  // Move this vehicle based on its behavior logic
  // Needs to read in the ArrayList of vehicles, the sensory field and width/height
  // -----------------------------
  void moveMe(ArrayList vehicles, SensoryField sfield,SensoryField tfield,SensoryField ffield, int w, int h) {  
    float ang;

    // make sure we're in bounds
    checkBounds(w,h);

    // update the wheel velocities based on the sense logic
    doTortLogic(tfield,sfield);
    doSenseLogic(sfield, ffield);

    speedmod=getSpeedRat();

    // move vehicle
    wheeldiff = wL.linvel - wR.linvel; // left and right wheel velocity difference
    angle += wheeldiff / axle;  // update the angle based on the difference of wheel velocities

    wheelavg = (wL.linvel + wR.linvel) / 2; // average wheel velocity to get overall vehicle velocity
    x += Math.cos(angle) * (wheelavg+.2)*speedmod; // update x based on the new angle and vehicle velocity
    y += Math.sin(angle) * (wheelavg+.2)*speedmod; // update y based on the new angle and vehicle velocity

    // check if we've run into another vehicle
    checkCollision(vehicles);

    // move wheels
    ang = angle + wangL; // left wheel angle relative to vehicle angle
    wL.x = x + halfaxle*(float)Math.cos(ang);
    wL.y = y + halfaxle*(float)Math.sin(ang);
    ang = angle + wangR; // right wheel angle relative to vehicle angle
    wR.x = x + halfaxle*(float)Math.cos(ang);
    wR.y = y + halfaxle*(float)Math.sin(ang);

    // move sensors
    ang = angle + sangL; // left sensor angle relative to vehicle angle
    sL.x = x + axle*(float)Math.cos(ang);
    sL.y = y + axle*(float)Math.sin(ang);
    ang = angle + sangR; // right sensor angle relative to vehicle angle
    sR.x = x + axle*(float)Math.cos(ang);
    sR.y = y + axle*(float)Math.sin(ang);
  }

  // Make sure we're in bounds
  // -----------------------------
  void checkBounds(int w, int h) {
    //    if (x < axle+5) x = axle+5;
    //    if (x > w-(axle+5)) x = w-(axle+5);
    //    if (y < axle+5) y = axle+5;
    //    if (y > h-(axle+5)) y = h-(axle+5);
    //Added roomba edge behavior so that character don't just try to run off the screen all the time
    if (x < axle+5) angle=angle+(float)Math.PI*2/3;
    if (x > w-(axle+5)) angle=angle+(float)Math.PI*2/3;
    if (y < axle+5) angle=angle+(float)Math.PI*2/3;
    if (y > h-(axle+5+80)) angle=angle+(float)Math.PI*2/3;

    if (x < axle+5) x = axle+5;
    if (x > w-(axle+5)) x = w-(axle+5);
    if (y < axle+5) y = axle+5;
    if (y > h-(axle+5+80)) y = h-(axle+5+80);
  }

  // Check for collision with other vehicles
  // -----------------------------
  void checkCollision(ArrayList vehicles) {
    float dx, dy, da;
    for (int i=0; i<vehicles.size(); i++) {
      Vehicle bv = (Vehicle)vehicles.get(i);
      if (!bv.equals(this)&&bv.axle>8) {
        // if it's not us then find the distance to vehicle
        dx = x-bv.x;
        dy = y-bv.y;
        // if the other vehicle is within our axle range
        if (dx*dx + dy*dy < axlesq*3) {
          // find the angle between us
          da = (float)Math.atan2(dy,dx);
          // shove past the other vehicle
          x = x + (float)Math.cos(da) * halfaxle;
          y = y + (float)Math.cos(da) * halfaxle;
          bv.x = bv.x + (float)Math.cos(da+(float)Math.PI) * halfaxle;
          bv.y = bv.y + (float)Math.sin(da+(float)Math.PI) * halfaxle;
        }
      }
    }
  }

  // Update the left wheel velocity
  // -----------------------------
  void setVelocityL(float vel) {
    wL.setVelocity(vel*MOVEVEL);
  }

  // Update the right wheel velocity
  // -----------------------------
  void setVelocityR(float vel) {
    wR.setVelocity(vel*MOVEVEL);
  }
}

