/***************************************************************************
* Dog: The dude that can herd the sheep. You give commands to him.
***************************************************************************/

/***************************************************************************
* Dog: That bad guy that chases after and eats sheep.
***************************************************************************/
class Dog
{
  /***************************************************************************
  * Variables
  ***************************************************************************/
  
  // List of states
  private static final int STATE_WAITING          = 0;
  private static final int STATE_START_HERDING    = 1;
  private static final int STATE_CIRCLING         = 2;
  private static final int STATE_BARK_CIRCLING    = 3;
  private static final int STATE_BACK_TO_WAITING  = 4;
  private static final int STATE_GO_GET_SHEEP     = 5;
  private static final int STATE_BRING_BACK_SHEEP = 6;
  
  // Info about icon
  private PImage m_img, m_barkImg;
  private float m_scale;
  
  // Location and movement
  private int m_x, m_y, m_direction;
  private int m_rotation;
  private float m_speed, m_maxSpeed;
  
  // List of all the sheep
  private ArrayList m_sheepList;
  int m_numActiveSheep;
  
  // Herding variables
  private int m_numSheepReadyToCircle;
  private int m_circleStart_x, m_circleStart_y;
  private int m_circleStart_radius;
  
  // Circling variables
  private int m_circlingPos;
  private int m_timeSpentBarking;
  private float m_dogSpace;
  private int m_timeSpentCircling;

  private static final int TIME_ALLOWED_CIRCLING = 240;
  private static final int MAX_TIME_SPENT_BARKING = 10;
  
  private Wolf m_wolf;
  
  // Bringing back individual sheep
  private Sheep m_bringBackSheep;
  
  // Current state
  private int m_currentState;
  
  
  /***************************************************************************
  * Constructor
  ***************************************************************************/
  public Dog(String imgLoc, String imgBarkLoc, float scale, int x, int y, ArrayList sheepList, int numSheep)
  {
    // Load and save the image
    m_img = loadImage(imgLoc);
    m_barkImg = loadImage(imgBarkLoc);
    m_scale = scale;
    
    // Starting position
    m_x = x;
    m_y = y;
    m_direction = 0;
    m_rotation = 0;
    m_speed = 0;
    
    // List of all the sheep
    m_sheepList = sheepList;
    m_numActiveSheep = numSheep;
    
    m_numSheepReadyToCircle = 0;
    m_circleStart_x = m_circleStart_y = m_circleStart_radius = -1;
    
    m_circlingPos = -1;
    m_timeSpentBarking = -1;
    m_timeSpentCircling = -1;
    
    m_wolf = null;
    
    m_dogSpace = m_img.height*m_scale/ 1; //1.5;
    
    m_bringBackSheep = null;
    
    // state
    m_currentState = STATE_WAITING;
  }
  
  
  /***************************************************************************
  * Getters/setters
  ***************************************************************************/
  
  public void setMaxSpeed(float s) { m_maxSpeed = s; }
  public void setDirection(int d) { m_direction = d; }
  
  public int getX() { return m_x; }
  public int getY() { return m_y; }
  
  public boolean isWaiting() { return m_currentState == STATE_WAITING; }
  public boolean isCircling() { return m_currentState == STATE_CIRCLING || m_currentState == STATE_BARK_CIRCLING; }
  
  public void oneLessActiveSheep() { m_numActiveSheep--; }
  
  
  /***************************************************************************
  * Drawing function
  ***************************************************************************/
  public void draw()
  {
    // Special drawing when circling
    if (m_currentState == STATE_CIRCLING || m_currentState == STATE_BARK_CIRCLING)
    { 
      PImage img = m_currentState == STATE_CIRCLING ? m_img : m_barkImg;
        
      imageMode(CENTER);
      
      // Rotate the image according to the current position on circle
      float rotation = radians(m_circlingPos - 270);
      
      translate(m_x, m_y);
      rotate(rotation);
      image(img, 0, 0, (m_img.width*m_scale), (m_img.height*m_scale));
      rotate(-rotation);
      translate(-m_x, -m_y);
    }
    else
    {
      imageMode(CORNER);
      image(m_img, m_x, m_y, (m_img.width*m_scale), (m_img.height*m_scale));
    }
    
    //TEMP
    if (false)
    {
      noFill();
      stroke(255,0,0);
      ellipse(m_circleStart_x, m_circleStart_y, 2*m_circleStart_radius, 2*m_circleStart_radius);
    }
  }
  
  
  /***************************************************************************
  * Moving function
  ***************************************************************************/
  public void move()
  {
    //println("Current state: " + m_currentState);
    
    if (m_currentState == STATE_WAITING)
    {
      m_x = windowW/2 - (int)(m_img.width * m_scale/2);
      m_y = windowH - (spaceBelowSheep/2);
    }
    else if (m_currentState == STATE_BACK_TO_WAITING)
    {
      moveBackToWaiting();
    }
    else if (m_currentState == STATE_START_HERDING)
    {
      moveToHerdingStart();
    }
    else if (m_currentState == STATE_CIRCLING)
    {
      moveCircling();
    }
    else if (m_currentState == STATE_BARK_CIRCLING)
    {
      moveBarking();
    }
    else if (m_currentState == STATE_GO_GET_SHEEP)
    {
      moveToBringBackSheep();
    }
    else if (m_currentState == STATE_BRING_BACK_SHEEP)
    {
      moveBringBackSheep();
    }
  }
  
  
  /***************************************************************************
  * Head back to waiting position
  ***************************************************************************/
  private void moveBackToWaiting()
  {
    int finalX = windowW/2 - (int)(m_img.width * m_scale/2);
    int finalY = windowH - (spaceBelowSheep/2);
    
    float changeInX = m_speed * cos(radians(m_direction));
    float changeInY = m_speed * sin(radians(m_direction));
    
    int direction = (int)(degrees(atan2(finalY - m_y, finalX - m_x)));
   
    // If within a certain distance of the final destination, just go
    // straight for it
    float distance = dist(finalX, finalY, m_x, m_y);
    if ((direction - m_direction > 45) || distance <= 5)
    {
      m_x = finalX;
      m_y = finalY;
      
      startWaiting();
      
      return;
    }
    
    // Make the x/y changes needed
    
    m_x += (int)changeInX;
    m_y += (int)changeInY;
    
    m_direction = direction;
  }
  
  
  /***************************************************************************
  * Head toward to the top of the herding circle
  ***************************************************************************/
  private void moveToHerdingStart()
  {
    int finalX = m_circleStart_x - (int)(m_img.width/2 * m_scale);
    int finalY = m_circleStart_y-m_circleStart_radius - (int)(m_img.height/2 * m_scale);
    
    float changeInX = m_speed * cos(radians(m_direction));
    float changeInY = m_speed * sin(radians(m_direction));
    
    float direction = (int)(degrees(atan2(finalY - m_y, finalX - m_x)));
   
    // If within a certain distance of the final destination, just go
    // straight for it
    float distance = dist(finalX, finalY, m_x, m_y);
    if ((direction - m_direction > 45) || distance <= 5)
    {
      m_x = finalX + (int)(m_img.width/2*m_scale);
      m_y = finalY + (int)(m_img.height/2*m_scale);
      startCircling();
      return;
    }
    

    // Make the x/y changes needed
    
    m_x += (int)changeInX;
    m_y += (int)changeInY;
  }
  
  /***************************************************************************
  * Run around the circle
  ***************************************************************************/
  private void moveCircling()
  {
    if (m_numSheepReadyToCircle >=  m_numActiveSheep)
      m_timeSpentCircling++;
    
    if (m_timeSpentCircling > TIME_ALLOWED_CIRCLING)
    {
      callDogBack();
      return;
    }
       
    // Go around the circle some more
    m_circlingPos = (int)(m_circlingPos - m_speed/2) % 360;
    
    // Figure out the x/y coordinates and the orientation at this position
    
    m_x = (int)(cos(radians(m_circlingPos)) * m_circleStart_radius + m_circleStart_x);
    m_y = (int)(sin(radians(m_circlingPos)) * m_circleStart_radius + m_circleStart_y);
    
    
    // Randomly adjust the speed every once in a while
    if (random(1.0) < 0.05)
    {
      m_speed = random(m_maxSpeed-0.5, m_maxSpeed);
    }
  }
  
  /***************************************************************************
  * Push the circle in bark  mode
  ***************************************************************************/
  private void moveBarking()
  {
    
    if (m_numSheepReadyToCircle >=  m_numActiveSheep)
      m_timeSpentCircling++;
    
    if (m_timeSpentCircling > TIME_ALLOWED_CIRCLING)
    {
      callDogBack();
      return;
    }
    
    m_timeSpentBarking++;
    if (m_timeSpentBarking >= MAX_TIME_SPENT_BARKING)
    {
      stopBarking(); return;
    }
    
    float changeInX = m_speed * cos(radians(m_direction));
    float changeInY = m_speed * sin(radians(m_direction));
    
    // Check if the move would put the circle past one of the boundaries
    
    float x1 = m_circleStart_x + changeInX - (m_circleStart_radius-m_dogSpace);
    float x2 = m_circleStart_x + changeInX + (m_circleStart_radius-m_dogSpace);
    float y1 = m_circleStart_y + changeInY - (m_circleStart_radius-m_dogSpace);
    float y2 = m_circleStart_y + changeInY + (m_circleStart_radius-m_dogSpace);
    
    if (x1 >= sheepBoundaryX1 && x2 <= sheepBoundaryX2 &&
        y1 >= sheepBoundaryY1 && y2 <= sheepBoundaryY2)
    {
      m_x += changeInX;
      m_y += changeInY;
      
      m_circleStart_x += changeInX;
      m_circleStart_y += changeInY;
    }
    else
    {
      stopBarking();
      return;
    }
  }
  
  
  /***************************************************************************
  * Head toward the sheep that we need to retrieve
  ***************************************************************************/
  private void moveToBringBackSheep()
  {
    if (m_bringBackSheep.isDeadOrDying())
    {
      callDogBack();
      return;
    }
    
    int finalX = m_bringBackSheep.getX();
    int finalY = m_bringBackSheep.getY();
    
    m_direction = (int)(degrees(atan2(finalY - m_y, finalX - m_x)));
    
    float changeInX = m_speed * cos(radians(m_direction));
    float changeInY = m_speed * sin(radians(m_direction));
   
    // If within a certain distance of the final destination, just go
    // straight for it
    float distance = dist(finalX, finalY, m_x, m_y);
    if (distance <= 5)
    {
      m_x = finalX;
      m_y = finalY;
      doneGettingSheep();
      return;
    }
    

    // Make the x/y changes needed
    
    m_x += (int)changeInX;
    m_y += (int)changeInY;
  }
  
  
  /***************************************************************************
  * Head toward the sheep that we need to retrieve
  ***************************************************************************/
  private void moveBringBackSheep()
  {
    int w = (int)(m_img.width * m_scale);
    int h = (int)(m_img.height * m_scale);
    
    int finalX = width/2 - m_bringBackSheep.getSize()/2;
    int finalY = height - spaceBelowSheep - wallWidth;
    
    m_direction = (int)(degrees(atan2(finalY - m_y, finalX - m_x)));
    
    float changeInX = m_speed * cos(radians(m_direction));
    float changeInY = m_speed * sin(radians(m_direction));
   
    // If within a certain distance of the final destination, just go
    // straight for it
    float distance = dist(finalX, finalY, m_x, m_y);
    if (distance <= m_speed)
    {
      m_x = finalX;
      m_y = finalY;
      doneBringingBackSheep();
      return;
    }
    
   
    // Check that we don't walk over the walls with the sheep
    if (abs(m_y + changeInY + m_bringBackSheep.getSize() - (height - spaceBelowSheep - wallWidth)) < 5)
    {
      if (m_x < wallLengthBottom || m_x + m_bringBackSheep.getSize() > width-wallLengthBottom)
      {
        m_direction = (m_x > width/2) ? 180 : 0;
    
        changeInX = m_speed * cos(radians(m_direction));
        changeInY = m_speed * sin(radians(m_direction));
      }
    }
    

    // Make the x/y changes needed
    m_x += (int)changeInX;
    m_y += (int)changeInY;
  }
  
  
  /***************************************************************************
  * Start waiting for commands.
  ***************************************************************************/
  public void startWaiting()
  {
    if (m_currentState != STATE_BACK_TO_WAITING)
    {
      println("Can't go straight to waiting - go through BACK_TO_WAITING first.");
      return;
    }
    
    m_currentState = STATE_WAITING;
  }
  
  
  /***************************************************************************
  * Start herding sheep.
  ***************************************************************************/
  public void startHerding()
  {
    
    if (m_currentState != STATE_WAITING)
      return;
    
    // Tell each sheep to get into start herding mode. This will get them
    // to bunch together into a circle.  They will tell the dog when they
    // are ready for circling.
    
    m_speed = m_maxSpeed * 3;
    
    m_numSheepReadyToCircle = 0;
    m_currentState = STATE_START_HERDING;
    m_timeSpentCircling = -1;
    
    // The center of the herding circle can be chosen randomly, and the radius 
    // depends on how many sheep there are
    
    int sheepSize = ((Sheep)(m_sheepList.get(0))).getSize();
    int sheepArea = sheepSize * sheepSize;
    
    m_circleStart_radius = (int)(sqrt(sheepArea * m_sheepList.size() / PI) + m_dogSpace);
    
    m_circleStart_x = (int)random(sheepBoundaryX1+m_circleStart_radius, sheepBoundaryX2-m_circleStart_radius);
    m_circleStart_y = (int)random(sheepBoundaryY1+m_circleStart_radius, sheepBoundaryY2-m_circleStart_radius);
    
    int finalX = m_circleStart_x - (int)(m_img.width/2 * m_scale);
    int finalY = m_circleStart_y-m_circleStart_radius - (int)(m_img.height/2 * m_scale);
    
    m_direction = (int)(degrees(atan2(finalY - m_y, finalX - m_x)));
    
    for (int s=0; s < m_sheepList.size(); s++)
    {
      Sheep sheep = (Sheep)m_sheepList.get(s);
      if (!sheep.isDeadOrDying() && !sheep.isDoneOrGettingDone())
      {
        sheep.startHerding(m_circleStart_x, m_circleStart_y, m_circleStart_radius - (int)(m_dogSpace));
      }
    }
  }
  
  /***************************************************************************
  * A new sheep is ready to circle.
  ***************************************************************************/
  public void sheepReadyToCircle(Sheep sheep)
  {
    m_numSheepReadyToCircle++;
    
    if (m_numSheepReadyToCircle >= m_numActiveSheep)
      m_timeSpentCircling = 0;
  }
  
  
  /***************************************************************************
  * Start circling.
  ***************************************************************************/
  private void startCircling()
  {
    m_currentState = STATE_CIRCLING;
    m_circlingPos = 270;
    m_speed = m_maxSpeed;
    
    if (m_wolf != null)
    {
      m_wolf.startHerding();
    }
  }
  
  
  /***************************************************************************
  * How much time left for circling?
  ***************************************************************************/
  public int timeLeftForCircling()
  {
    if ((m_currentState == STATE_CIRCLING || m_currentState == STATE_BARK_CIRCLING)
        && m_numSheepReadyToCircle >=  m_numActiveSheep)
    {
      return (TIME_ALLOWED_CIRCLING - m_timeSpentCircling) / frameRate;
    }
    else
    {
      return -1;
    }
  }
  
  
  /***************************************************************************
  * Is the given location within the bounds of the dog's circling radius?
  ***************************************************************************/
  public boolean withinCirclingRadius(int x, int y)
  {
    return (dist(x,y, m_circleStart_x, m_circleStart_y) < m_circleStart_radius + 10);
  }
  
  
  /***************************************************************************
  * Does the given line cross the circling radius?
  ***************************************************************************/
  public boolean crossingCirclingRadius(int x1, int y1, int x2, int y2)
  {
    return false;
  }
  
  
  /***************************************************************************
  * Bark button pressed
  ***************************************************************************/
  public void barkButtonPressed()
  {
    if (m_currentState == STATE_CIRCLING)
    {
      startBarking();
    }
  }
  
  /***************************************************************************
  * Start bark
  ***************************************************************************/
  private void startBarking()
  {
    if (m_currentState != STATE_CIRCLING ||
        m_numSheepReadyToCircle < m_numActiveSheep)
    {
      return;
    }
    
    m_currentState = STATE_BARK_CIRCLING;
    m_timeSpentBarking = 0;
    
    // Move the circle in the direction between the dog's current location
    // and the circle's current center
    m_direction = (int)(degrees(atan2(m_circleStart_y - m_y, m_circleStart_x - m_x)));
    
    // Tell the sheep we started barking
    for (int s=0; s < m_sheepList.size(); s++)
    {
      Sheep sheep = (Sheep)m_sheepList.get(s);
      sheep.startBark(m_direction, m_speed);
    }
  }
  
  
  /***************************************************************************
  * Stop bark
  ***************************************************************************/
  private void stopBarking()
  {
    m_currentState = STATE_CIRCLING;
    
    // Tell the sheep we stopped barking
    for (int s=0; s < m_sheepList.size(); s++)
    {
      Sheep sheep = (Sheep)m_sheepList.get(s);
      sheep.stopBark(m_circleStart_x, m_circleStart_y, m_circleStart_radius - (int)m_dogSpace);
    }
  }
  
  
  /***************************************************************************
  * Stop dog from going whatever he is doing and bring him back to waiting
  ***************************************************************************/
  public void callDogBack()
  {
    if (m_currentState == STATE_START_HERDING)
    {
      for (int s=0; s < m_sheepList.size(); s++)
      {
        Sheep sheep = (Sheep)m_sheepList.get(s);
        sheep.stopHerding();
      }

      if (m_wolf != null)
      {
        m_wolf.stopHerding();
      }
    }
    else if (m_currentState == STATE_CIRCLING)
    {
      for (int s=0; s < m_sheepList.size(); s++)
      {
        Sheep sheep = (Sheep)m_sheepList.get(s);
        sheep.stopCircling();
      }

      if (m_wolf != null)
      {
        m_wolf.stopHerding();
      }
    }
    else if (m_currentState == STATE_BARK_CIRCLING)
    {
      for (int s=0; s < m_sheepList.size(); s++)
      {
        Sheep sheep = (Sheep)m_sheepList.get(s);
        sheep.stopCircling();
      }
    }
    else if (m_currentState == STATE_BRING_BACK_SHEEP && m_bringBackSheep != null)
    {
      // This means the dog was called back in the middle of bringing back the sheep
      m_bringBackSheep.cancelBringingBack();
      m_bringBackSheep = null;
    }
    
    
    m_speed = m_maxSpeed * 2;
    
    m_timeSpentCircling = 0;
    
    int finalX = windowW/2 - (int)(m_img.width * m_scale/2);
    int finalY = windowH - (spaceBelowSheep/2);
    m_direction = (int)(degrees(atan2(finalY - m_y, finalX - m_x)));
    
    m_currentState = STATE_BACK_TO_WAITING;
  }
  
  /***************************************************************************
  * A wolf has arrived!
  ***************************************************************************/
  public void wolfArrived(Wolf theWolf)
  {
    m_wolf = theWolf;
  }
  
  /***************************************************************************
  * Wolf is gone again.
  ***************************************************************************/
  public void wolfLeft()
  {
    m_wolf = null;
  }
  
  /***************************************************************************
  * Retrieve the specified sheep
  ***************************************************************************/
  public void goGetSheep(Sheep s)
  {
    if (m_currentState != STATE_WAITING)
      return;
      
    m_currentState = STATE_GO_GET_SHEEP;
    m_speed = m_maxSpeed;
    m_bringBackSheep = s;
  }
  
  /***************************************************************************
  * Done getting the sheep - now what?
  ***************************************************************************/
  private void doneGettingSheep()
  {
    bringBackSheep();
  }
  
  /***************************************************************************
  * Bring back the sheep
  ***************************************************************************/
  private void bringBackSheep()
  {
    m_currentState = STATE_BRING_BACK_SHEEP;
    m_speed = m_maxSpeed;
    m_bringBackSheep.startBringingBack();
  }
  
  /***************************************************************************
  * Done getting the sheep - now what?
  ***************************************************************************/
  private void doneBringingBackSheep()
  {
    m_bringBackSheep.doneBringingBack();
    m_bringBackSheep = null;
    callDogBack();
  }
  
  
}// end class Dog
