// Buddy


class Buddy extends PhysicsObject {
  // Constants
  float maxVel = 30;
  float frictionGround = 0.8;
  int STATE_IDLE = 0;
  int STATE_FOLLOWING_PLAYER = 1;
  int STATE_EXCITED = 2;
  // Properties
  String myName = "Buddy"; // will be overridden by Beep and Boop
  boolean isOnGround = true;
  int direction = 1; // -1 or 1 (left or right)
  float angleVel;
  float wVel,hVel;
  float naturalW=60,naturalH=60; // in a perfect world without squash/stretch, my width and height. Different between guys.
  float angle;
  color myFill;
  color myStroke;
  int currentState=STATE_IDLE;
  float timeUntilNextEagerHop; // in SECONDS. For idle animation.
  float timeUntilStopVibrating = 0; // in SECONDS
  // Components
  Face face;
  SpeechBubble speechBubble;
  
  Buddy() {
    x = y = 0;
    w = naturalW;
    h = naturalH;
    wVel = hVel = 0;
    angle = 0;
    angleVel = 0;
    face = new Face();
    speechBubble = new SpeechBubble(this);
    makeBody(new Vec2(x,y), w);
  }
  void makeBody(Vec2 center, float d_) {
    // first, try to kill body
    killBody();
    
    CircleDef cd = new CircleDef();
    float box2dD = box2d.scalarPixelsToWorld(d_*4)/WS;
    cd.radius = box2d.scalarPixelsToWorld(box2dD);
    //if (myCategoryBits!=-1) cd.filter.categoryBits = myCategoryBits;
    //if (myMaskBits!=-1) cd.filter.maskBits = myMaskBits;
    // Parameters that affect physics
    cd.density = 1.0f;
    cd.friction = 2f;
    cd.restitution = 0.5f;
    // Define the body and make it from the shape
    BodyDef bd = new BodyDef();
    bd.position.set(box2d.coordPixelsToWorld(new Vec2(center.x, center.y)));
    bd.fixedRotation = true;
    body = box2d.createBody(bd);
    body.createShape(cd);
    body.setMassFromShapes();
    body.setLinearDamping(0.0f);
    body.setLinearVelocity(new Vec2(0, 0));
    body.setAngularVelocity(0);
    body.setAngularDamping(1); //40
    body.allowSleeping(false);
  }
  
  
  
  void update() {
    // Speech bubba!
    speechBubble.update();
    // Math!
    if (currentState == STATE_IDLE) idleStateMath();
    else if (currentState == STATE_FOLLOWING_PLAYER) followingPlayerMath();
    else if (currentState == STATE_EXCITED) excitedStateMath();
    applyGravity();
    groundDetection();
    updatePos();
    terminalVelocity();
    visualMath();
    body.setXForm(box2d.coordPixelsToWorld(new Vec2(x/WS, y/WS)), angle);
  }
  void draw() {
    // Speech bubba!
    speechBubble.draw();
    // Me!
    pushMatrix();
    translate(x,y);
    if (timeUntilStopVibrating > 0) {
      offsetVibratingPosition();
      timeUntilStopVibrating -= deltaTime;
    }
    scale(direction, 1);
    rotate(angle);
    drawBody();
    //translate(0,-h*0.5);
    face.draw();
    popMatrix();
  }
  void drawBody() {
    fill(myFill);
    stroke(myStroke);
    ellipse(0,0, w,h);
  }
  void offsetVibratingPosition() {
    translate(random(-20,20), random(-20,20));
    rotate(random(-0.2,0.2));
  }
  
  void applyGravity() {
    body.setLinearVelocity(new Vec2(body.getLinearVelocity().x, body.getLinearVelocity().y-GRAVITY_DEFAULT));
  }
  
  void updatePos() {
    x = box2d.getBodyPixelCoord(body).x * WS;
    y = box2d.getBodyPixelCoord(body).y * WS;
    //angle = body.getAngle(); //get its angle of rotation
  }
  void terminalVelocity() {
    float speed = sqrt(body.getLinearVelocity().x*body.getLinearVelocity().x + body.getLinearVelocity().y*body.getLinearVelocity().y);
    if (speed > maxVel) {
      float speedAngle = atan2(body.getLinearVelocity().y, body.getLinearVelocity().x);
      body.setLinearVelocity(new Vec2(cos(speedAngle)*maxVel, sin(speedAngle)*maxVel));
    }
  }
  void groundDetection() {
    isOnGround = false; // We'll say otherwise shortly.
    //bodyIAmOn = null; // We'll say otherwise shortly.
    // go through list of things my feet are touching; if it's something I can stand on, consider onGround to be true.
    ContactEdge edge = body.getContactList();
    while (edge!=null) {
      if (edge.other.getUserData()=="boundary") {
        isOnGround = true;
        //bodyIAmOn = edge.other; // set bodyIAmOn
      }
      edge = edge.next;
    }
  }
  
  
  void visualMath() {
    // angle!
    angle += angleVel;
    angleVel += (0-angle) / 7;
    if (isOnGround) angleVel *= 0.9;
    else angleVel *= 0.9;
    // width and height!
    w += wVel;
    h += hVel;
    wVel += (naturalW-w) / 7;
    hVel += (naturalH-h) / 8;
    wVel *= 0.8;
    hVel *= 0.8;
  }
  
  void idleStateMath() {
    // Hop eagerly! This is exciting!
    timeUntilNextEagerHop -= deltaTime;
    if (timeUntilNextEagerHop <= 0) {
      if (isOnGround) {
        body.setLinearVelocity(new Vec2(random(-0.5,0.5), random(4,6)));
        timeUntilNextEagerHop = random(1,5);
      }
    }
  }
  void excitedStateMath() {
    // Hop VERY EAGERLY!
    timeUntilNextEagerHop -= deltaTime;
    if (timeUntilNextEagerHop <= 0) {
      if (isOnGround) {
        body.setLinearVelocity(new Vec2(random(-1,1), random(7,8)));
        angle = random(-1,1);
        timeUntilNextEagerHop = 0.1;
      }
    }
  }
  void followingPlayerMath() {
    if (isOnGround) {
      body.setLinearVelocity(new Vec2(random(2,4)*sign(player.x-x), random(4,10)));
      angleVel = random(-0.3,0.3);
      //w *= 1.1;
      h *= 0.7;
      wVel = 4;
      //hVel = -2;
    }
    // DONE following player??
    if (abs(x-player.x) < player.w*0.5 && abs(y-(player.y)) < player.h) {
      onReachedPlayer();
    }
  }
  
  
  // ================================ EVENTS ================================
  void speak(String speechText) {
    float X = x + 50;
    float Y = y-h - 100;
    speechBubble.appear(X,Y, speechText);
  }
  void onReachedPlayer() {
    sequenceController.alert(myName + "ReachedPlayer");
  }
  void setState(int newState) {
    currentState = newState;
  }
  void addVelocity(float xForce, float yForce) {
    body.setLinearVelocity(new Vec2(body.getLinearVelocity().x+xForce, body.getLinearVelocity().y+yForce));
  }
  
  
  
  
  class Face {
    float pupilsX = 0;
    float pupilsY = 0;
    float pupilScale = 1; // normally 1, but less for surprised
    float eyePosX = 9;
    float eyePosY = -8;
    float faceScale; // set in my constructor, based on the size of my guy
    private PImage eyeImg;
    private PImage mouthImg;
    private float imageScale = 0.2;
    private float timeUntilOpenEyes = 0;
    private float mouthScale = 0.3;
    private float timeUntilBlink = 0;
    private float timeUntilNotSurprised = 0;
    private PImage eyeHappy_img;
    private PImage eyeShut_img;
    private PImage eyeWideOpen_img;
    private PImage mouthSurprised_img;
    private PImage mouthHappy_img;
    
    Face() {
      // How big is my face yoo!
      faceScale = (naturalW+naturalH) / 90;
      
      // Assign my images!
      eyeHappy_img = beepEyeHappy_img;
      eyeShut_img = beepEyeShut_img;
      eyeWideOpen_img = beepEyeWideOpen_img;
      mouthHappy_img = beepMouthHappy_img;
      mouthSurprised_img = beepMouthSurprised_img;
    }
    void draw() {
      // Update
      timeUntilBlink -= deltaTime;
      if (timeUntilBlink <= 0) {
        blink();
      }
      if (timeUntilOpenEyes > 0) {
        timeUntilOpenEyes -= deltaTime;
        eyeImg = eyeShut_img;
        if (direction != 0) mouthImg = mouthSurprised_img;
        else mouthImg = mouthHappy_img;
      }
      else if (timeUntilNotSurprised > 0) {
        eyeImg = eyeHappy_img;//eyeWideOpen_img;
        mouthImg = mouthSurprised_img;//mouthHappy_img
      }
      else {
        eyeImg = eyeHappy_img;
        mouthImg = mouthHappy_img;
      }
      if (timeUntilNotSurprised > 0) {
        timeUntilNotSurprised -= deltaTime;
        if (timeUntilNotSurprised <= 0) {
          timeUntilNotSurprised = -1;
          blink();
        }
      }
      
      // Draw
      pushMatrix();
      scale(w/naturalW*faceScale,h/naturalH*faceScale);
      // determine images
      
      // --- EYES ---
      // - left -
      pushMatrix();
      translate(-eyePosX,eyePosY);
      rotate( 0.16);
      image(eyeImg, -eyeImg.width/2*imageScale,-eyeImg.height/2*imageScale, eyeImg.width*imageScale,eyeImg.height*imageScale);
      if (timeUntilNotSurprised>0) {
        scale(-1,1);
        //image(eyeShut_img, -eyeShut_img.width/2*imageScale,-eyeShut_img.height/2*imageScale-8, eyeShut_img.width*imageScale,eyeShut_img.height*imageScale);
      }
      popMatrix();
      // - right -
      pushMatrix();
      translate( eyePosX,eyePosY);
      rotate(-0.16);
      image(eyeImg, -eyeImg.width/2*imageScale,-eyeImg.height/2*imageScale, eyeImg.width*imageScale,eyeImg.height*imageScale);
      if (timeUntilNotSurprised>0) {
        scale(-1,1);
        //image(eyeShut_img, -eyeShut_img.width/2*imageScale,-eyeShut_img.height/2*imageScale-11, eyeShut_img.width*imageScale,eyeShut_img.height*imageScale);
      }
      popMatrix();
      
      if (timeUntilOpenEyes <= 0) {
        // -- PUPILS --
        // - left -
        drawPupil(-eyePosX+pupilsX*8,eyePosY+pupilsY*6);
        // - right -
        drawPupil( eyePosX+pupilsX*8,eyePosY+pupilsY*6);
      }
      
      // --- MOUTH --
      pushMatrix();
      translate(4,8);
      rotate(-0.11);
      image(mouthImg, -mouthImg.width/2*mouthScale,-mouthImg.height/2*mouthScale, mouthImg.width*mouthScale,mouthImg.height*mouthScale);
      popMatrix();
      
      popMatrix();
    }
    void drawPupil(float X,float Y) {
      noStroke();
      fill(32);
      ellipse(X,Y, 10*pupilScale,10*pupilScale);
      fill(250);
      ellipse(X+1.2,Y-2.6, 2,2);
    }
    void blink() {
      timeUntilBlink = random(1)<0.2 ? 0.1 : random(2,4);
      timeUntilOpenEyes = 0.1;
      pupilsX = random(-0.7,0.7);
      pupilsY = random(-0.6,0.3);
      pupilScale = 1;
    }
    
    void becomeSurprised() {
      timeUntilNotSurprised = 1;
      pupilsX = 0;
      pupilsY = 0.4;
      pupilScale = 0.7;
    }
  }
  
}







