#include "AttackPath.h"

AttackPath::AttackPath()
{
  clear();
}

AttackPath::~AttackPath()
{
  clear();
}

//set parameters. Returns false if rad if 0 or the state is not invalid;
bool AttackPath::setInitialRadius (uint8_t rad)
{
  if (attackPathIsValid)
    return false;

  if (rad == 0)
    return false;

  radius=rad;
  return true;
}

uint8_t AttackPath::getInitialRadius ()
{
  return radius;
}

//returns the radius of a point based on the amount of time that has elapsed.
uint8_t AttackPath::getCurrentRadius (int timeElapsed)
{
  //Easy cases: return the initial radius if no time elapsed or if the time per segment is zero.
  if (timeElapsed == 0 || getTimePerSegment() == 0)
    return radius;

  //Now for the long plan.
  //Radius is based on how much time elapsed.
  FixedInt timeRatio(timeElapsed);
  timeRatio/=getTimePerSegment();

  if ((timeRatio * 100).toInt() < 30)
    {
      //The first 30% of time, shrink the radius from 100% to 80%
      //Ratio = (-2/3)T + 1
      timeRatio = timeRatio * -2 / 3;
      timeRatio += 1;
      timeRatio *= radius;
      return timeRatio.toInt();
    }
  else if ((timeRatio * 100).toInt() < 80)
    {
      //30% to 80% of time, shrink the radius from 80% to 50%
      //Ratio = (-3/5)T + .98
      timeRatio = timeRatio * -3 / 5;
      timeRatio += FixedInt(.98);
      timeRatio *= radius;
      return timeRatio.toInt();
    }
  else if ((timeRatio * 100).toInt() < 90)
    {
      //80% to 90% of time, shrink the radius from 50% to 30%
      //Ratio = -2T + 2.1
      timeRatio = timeRatio * -2;
      timeRatio += FixedInt(2.1);
      timeRatio *= radius;
      return timeRatio.toInt();
    }
  else
    {
      //90% onward, shrink the radius from 30% to 0%
      //Ratio = -3T + 3
      timeRatio = timeRatio * -3;
      timeRatio += 3;
      timeRatio *= radius;
      return timeRatio.toInt();
    }

  return 0;
}

//set bounds. Returns false if w or h is 0 or the state is not invalid.
bool AttackPath::setBounds (uint16_t w, uint16_t h)
{
  if (attackPathIsValid)
    return false;

  if (w==0 || h==0)
    return false;

  boundsW=w;
  boundsH=h;
  return true;
}

//set the axis the coordinates are relative to.
bool AttackPath::setAxis (int x, int y)
{
  if (attackPathIsValid)
    return false;

  axisX = x; axisY = y;
  return true;
}

//Returns false if the x y coordinate is out of bounds, or state is not invalid. You cannot stack the first two points at the same initial position.
bool AttackPath::addPoint (int x, int y)
{
  if (attackPathIsValid)
    return false;

  if (x < boundsW/-2)
    x=boundsW/-2;
  if (y < boundsH/-2)
    y=boundsH/-2;
  if (x > boundsW/2)
    x=boundsW/2;
  if (y > boundsH/2)
    y=boundsH/2;

  if (points.size()==1 && x+axisX == points[0].getPosX() && y+axisY == points[0].getPosY())
    return false;

  //passed validity. Make a HitBox and set it.
  HitBox ans;
  vector <int> info;
  info.push_back(radius);

  ans.setPosition (axisX+x, axisY+y);
  ans.setShape("Circle", info);

  points.push_back(ans);
  return true;
}

//Try to validate the given data. If it is valid, return an iterator to the first point.
//If it is invalid, return an iterator to the end.
bool AttackPath::validateAndInitialize()
{
  if (!validate())
    return false;
  attackPathIsValid=true;
  return true;
}

bool AttackPath::isValid()
{
  return attackPathIsValid;
}

//Tell the attack path to only show the first point.
//This will try to validate the given data and return points.end() if it is not valid.
vector <HitBox> :: iterator AttackPath::getFirstPoint ()
{
  if (!validate())
    return points.end();

  return points.begin();
}

//clears all data, sets data to be invalid
void AttackPath::clear()
{
  attackPathIsValid=false;
  points.clear();
  radius=1;
  boundsW=1; boundsH=1;
  axisX=0;axisY=0;
  timePerSegment=0;
}

//returns true
bool AttackPath::validate()
{
  //if it's already drawing then it works.
  if (attackPathIsValid)
    return true;

  //Need at least 1 point
  if (points.size() < 1)
    return false;

  return true;
}

//Given a direction and distance, create two points that are perpendicular to the origin
int AttackPath::generatePerpendicularPoints(int directionX, int directionY, int distance, vector <FixedInt> & ans)
{
  //If Line A connects the origin to (directionX, directionY)
  //and Line B connects the two points found in ans

  //If Ax and Ay = 0? Fail, this isn't a direction. Return -1.
  //If Ax or Ay = 0? Deploy easily solved special cases! Return 0.
  //For everything else?
  //The math:
  //The dot product of A.B must equal 0 (A and B are perpendicular)
  // Ax*Bx+Ay*By=0
  //Each endpoint of B is also distance away from the origin
  // Bx*Bx+By*By=distance*distance
  //Whee, substitution math gives us
  //By = (-1 * Ax / Ay)*Bx
  //Bx^2 * (1 + Ax^2/Ay^2) - distance^2 = 0
  //Let Q = (1 + Ax^2/Ay^2)
  //Quadratic Formula sez:
  //Bx = +- 1 * distance * sqrt(Q) / Q
  //You can back solve to get By.
  //Return 0;

  ans.clear();

  //If Ax and Ay = 0? Fail, this isn't a direction.
  if (directionX == 0 && directionY == 0)
    return -1;

  //If Ax or Ay = 0? Deploy easily solved special cases!
  //If the direction's vertical, the perpendicular points are horizontal.
  if (directionX == 0)
    {
      ans.push_back(FixedInt(distance)); ans.push_back(0);
      ans.push_back(FixedInt(-1 * distance)); ans.push_back(0);
      return 0;
    }
  //If the direction's horizontal, the perpendicular points are vertical.
  if (directionY == 0)
    {
      ans.push_back(0); ans.push_back(FixedInt(-1 * distance));
      ans.push_back(0); ans.push_back(FixedInt(distance));
      return 0;
    }

  //For everything else?
  //The math:
  //The dot product of A.B must equal 0 (A and B are perpendicular)
  // Ax*Bx+Ay*By=0
  //Each endpoint of B is also distance away from the origin
  // Bx*Bx+By*By=distance*distance
  //Whee, substitution math gives us
  //By = (-1 * Ax / Ay)*Bx
  //Bx^2 * (1 + Ax^2/Ay^2) - distance^2 = 0
  //Let Q = (1 + Ax^2/Ay^2)
  //Quadratic Formula sez:
  //Bx = +- 1 * distance * sqrt(Q) / Q
  //You can back solve to get By.

  FixedInt ax, ay, ax2, ay2, q, sqrtq, bx, by;

  ax=directionX; ay=directionY; ax2=ax*ax; ay2=ay*ay;
  q=(ax2/ay2)+1; sqrtq=fixSqrt(q);

  //I'd like the first point to be 64 degrees forward of the direction. Useful when I want all four points in circular order and I call this twice.
  bx=sqrtq*distance*-1;
  bx/=q;
  by=ax * bx * -1 / ay;
  ans.push_back(bx);
  ans.push_back(by);

  bx=sqrtq*distance;
  bx/=q;
  by=ax * bx * -1 / ay;
  ans.push_back(bx);
  ans.push_back(by);

  return 0;
}

//Given two points and the distance from them, generates four coordinates that are right angles to each other and distance away from the two points.
int AttackPath::generateRectangleBasedOnPoints (int ax, int ay, int bx, int by, int distance, vector<FixedInt> & ans)
{
  ans.clear();

  //if a and b are the same point, reject.

  if (ax==bx && ay==by)
    return -1;

  vector<FixedInt>tempAns;
  vector<FixedInt>::iterator ansIter;

  //get points perpendicular to a; must move a to the origin to get the correct direction
  generatePerpendicularPoints(bx-ax, by-ay, distance, tempAns);
  tempAns[0]+=ax;
  tempAns[1]+=ay;
  tempAns[2]+=ax;
  tempAns[3]+=ay;
  ansIter=tempAns.begin();
  while (ansIter!=tempAns.end())
    {
      ans.push_back(*ansIter);
      ansIter++;
    }
  tempAns.clear();

  //get points perpendicular to b; must move b to the origin to get the correct direction
  generatePerpendicularPoints(ax-bx, ay-by, distance, tempAns);
  tempAns[0]+=bx;
  tempAns[1]+=by;
  tempAns[2]+=bx;
  tempAns[3]+=by;
  ansIter=tempAns.begin();
  while (ansIter!=tempAns.end())
    {
      ans.push_back(*ansIter);
      ansIter++;
    }
  tempAns.clear();
  //I want to insert the points in order, so that the lines connecting consecutive points are at right angles to each other.
  //Dot product = 0 if they are at right angles, ho!

  //The first four points determine the direction. We will keep or swap the last four points accordingly.
  FixedInt c1x, c1y, c2x, c2y, swapx, swapy;

  //dimensions of the first line
  c1x=ans[2]-ans[0];
  c1y=ans[3]-ans[1];

  //dimensions of the second line
  c2x=ans[4]-ans[2];
  c2y=ans[5]-ans[3];

  //The dot product c1.c2 should equal 0. If it doesn't, we need to reverse the last two points.
  if ((c1x * c2x + c1y * c2y).toInt() < -5 || (c1x * c2x + c1y * c2y).toInt() > 5)
    {
      //reverse the two points.
      swapx=ans[4]; ans[4]=ans[6]; ans[6]=swapx;
      swapy=ans[5]; ans[5]=ans[7]; ans[7]=swapy;
    }
  return 0;
}

//When getting data for the preview:
//points will be filled with circles for the hit points of the attack.
//lines contain the coordinates for the line segments connecting the points.
//The pointer is three coordinates making a triangle that connects the first point to the second so the user knows which way to start drawing.
void AttackPath::drawPreview (vector<HitBox*> & points2, vector<int> & lines, vector<int> & pointer)
{
  points2.clear(); lines.clear(); pointer.clear();
  vector<int>::iterator intVectorIter;

  //points2 is simply filled.
  vector<HitBox>::iterator pointIter=points.begin();
  while (pointIter != points.end())
    {
      points2.push_back(&*pointIter);
      pointIter++;
    }

  //pointer has the coordinates of the triangle that connects the first two points.
  vector<FixedInt> pointerFix;

  //Don't draw a pointer if there is only one point in the pattern.
  if (points2.size()>1)
    {
      //Do not continue if the line is degenerate
      if (generatePerpendicularPoints(points2[1]->getPosX()-points2[0]->getPosX(),
                                      points2[1]->getPosY()-points2[0]->getPosY(),
                                      points2[0]->getRadius(),
                                      pointerFix)==0)
        {
          //fill pointer with pointerFix's results.
          for (int i = 0; i < int(pointerFix.size()/2); i++)
            {
              pointer.push_back(pointerFix[2*i].toInt()+points2[0]->getPosX());
              pointer.push_back(pointerFix[2*i+1].toInt()+points2[0]->getPosY());
            }
          //Now add the end point.
          pointer.push_back(points2[1]->getPosX());
          pointer.push_back(points2[1]->getPosY());
        }
    }

  //lines is a list of coordinates of all the points.
  pointIter=points.begin();
  while (pointIter != points.end())
    {
      lines.push_back(pointIter->getPosX());
      lines.push_back(pointIter->getPosY());
      pointIter++;
    }

}

//This gets the next two points and rectangle segment connecting them.
//If there is less than two points left, segment is empty and points has 0 or 1 indecies.
void AttackPath::drawTwoPoints (int index, vector<HitBox*>& points2, vector<int>& segment, int startTime)
{
  points2.clear(); segment.clear();

  //if out of bounds, return
  if (index >= int(points.size()))
    return;

  //if almost out of bounds, only add the last coordinate
  if (index == int(points.size()-1))
    {
      points2.push_back(&points[points.size()-1]);
      return;
    }

  vector<FixedInt>segmentFix;
  vector<FixedInt>::iterator fixedIntVectorIter;
  int ax, ay, bx, by, distance;

  //Get info.
  ax=points[index].getPosX();
  ay=points[index].getPosY();

  bx=points[index+1].getPosX();
  by=points[index+1].getPosY();

  distance=getCurrentRadius(SDL_GetTicks()-startTime);

  //Add the two points from the index.
  points2.push_back(&points[index]);
  points2.push_back(&points[index+1]);

  //get the endpoints of the segment
  if (generateRectangleBasedOnPoints (ax, ay, bx, by, distance, segmentFix)
      ==0)
    {
      fixedIntVectorIter=segmentFix.begin();
      while (fixedIntVectorIter!=segmentFix.end())
        {
          segment.push_back(fixedIntVectorIter->toInt());
          fixedIntVectorIter++;
        }
    }
}

void AttackPath::drawOnlyFirstPoint (int index, vector<HitBox*>& points2)
{
  points2.clear();

  //if out of bounds, return
  if (index >= int(points.size()))
    return;

  points2.push_back(&points[index]);
}

int AttackPath::getNumberOfPoints()
{
  return points.size();
}


//The user has a certain amount of time to draw each segment.
void AttackPath::setTimePerSegment(int tim)
{
  if (tim < 0)
    return;

  timePerSegment=tim;
}

int AttackPath::getTimePerSegment()
{
  return timePerSegment;
}
