#include "stadium.hpp"

#include "playstate.hpp"
#include "stage.hpp"
#include "physicsobject.hpp"

#include "spaces/space.hpp"
#include "tiles/tile.hpp"
#include "entities/mixtape.hpp"

/*
// test
#include "character/character.hpp"
#include "character/character_modelview.hpp"
#include "character/character_moves.hpp"
*/

Stadium::Stadium(const KungFlowGame *game, const PlayState *playState, Stage *stage) : game(game), playState(playState), stage(stage) {

  this->flowAxis = 0;
  this->flowDirection.coords[flowAxis] = 1.0f;
  this->flowPosition.coords[abs(1 - flowAxis)] = 0;
  this->flowPosition.coords[flowAxis] = 2;

  SetFlowTilesPerBeat(0.0f);

  //this->flowVelocity = flowTilesPerBeat / (playState->GetCurrentMusic()->GetBeatDuration_ms() * 0.001f);
  //this->flowMovement = flowDirection * flowVelocity;

//  for (unsigned int i = 0; i < sizeof(rndTmp) / sizeof(float); i++) {
//    rndTmp[i] = Random(0.0f, 1.0f);
//  }

//  backgroundPositioner = new SpatialNode(&playState->GetWorldNode());
//  backgroundPositioner->spatial.scale = Vector2(1.0f, 0.2f); // less influence by up-down movement

  float backgroundScale = 1.0f;
  float parallaxScale = 1.0f;
  backgroundSprite[0] = new Sprite();
  backgroundSprite[0]->LoadFile("res/gfx/background01.png", e_FilterMode_Linear);
  backgroundSprite[0]->SetAlpha(1.0f);
  backgroundObject[0] = new Object(&playState->GetWorldNode(), backgroundSprite[0]);
  backgroundObject[0]->spatial.z = -65.0f * parallaxScale;
  backgroundObject[0]->spatial.position = Vector2(0, -220);
  backgroundObject[0]->spatial.scale = Vector2(512, 64) * backgroundScale * parallaxScale;// * Vector2(1.0f, 1.0f / 0.2f);
  backgroundObject[0]->spatial.orientation = playState->GetViewOrientation();

  backgroundScale = 1.5f;
  parallaxScale = 2.0f;
  backgroundSprite[1] = new Sprite();
  backgroundSprite[1]->LoadFile("res/gfx/background02.png", e_FilterMode_Linear);
  backgroundSprite[1]->SetAlpha(1.0f);
  backgroundObject[1] = new Object(&playState->GetWorldNode(), backgroundSprite[1]);
  backgroundObject[1]->spatial.z = -65.0f * parallaxScale;
  backgroundObject[1]->spatial.position = Vector2(0, -250);
  backgroundObject[1]->spatial.scale = Vector2(512, 64) * backgroundScale * parallaxScale;
  backgroundObject[1]->spatial.orientation = playState->GetViewOrientation();


  // generate map layout

  numSpaces = 50;
  spacesLoaded = 0;
  spaceGenConfig.minLength = 7;
  spaceGenConfig.maxLength = 20;
  spaceGenConfig.minWidth = 5;//12
  spaceGenConfig.maxWidth = 12;//16
  spaceGenConfig.widthVariation = 3;
  spaceGenConfig.maxSeparatorLength = 0;
  spaceGenConfig.minDescent = 0;
  spaceGenConfig.maxDescent = 3;
  lastSpaceZ = numSpaces * (spaceGenConfig.maxDescent - spaceGenConfig.minDescent) * 1.1f + 5;

  UpdateTriggerPosition(Vector2(0), false);


/*
  isoTileSprite1 = new Sprite();
  isoTileSprite1->LoadFile("res/gfx/isotile3.png", e_FilterMode_Nearest);
  isoTileSprite2 = new Sprite();
  isoTileSprite2->LoadFile("res/gfx/isotile4.png", e_FilterMode_Nearest);
  isoTileSprite3 = new Sprite();
  isoTileSprite3->LoadFile("res/gfx/isotile5.png", e_FilterMode_Nearest);
*/

/* our old 'test level' <3
  for (int x = 0; x < dimensions[0]; x++) {
    float baseZ = 4;
    for (int y = 0; y < dimensions[1]; y++) {
      if (y % 8 == 0) baseZ -= 1;//round(Random(1, 2));

      //bool funnyBool = true;//(x % 4 != 0 || ((x + y) % 6 == 0 || (x + y + 1) % 6 == 0)); // hedges
      //funnyBool = y < 10 || (x % 3 != 0 || ((x + y) % 4 == 0 || (x + y + 1) % 4 == 0 || (x + y + 2) % 4 == 0)); // lights
      //if (x == 0 || y == 0 || x == dimensions[0] - 1 || y == dimensions[1] - 1) funnyBool = false; // edges

      //float zpos = 0.0f;
      //if (y < 8) zpos = 1.0f * 0.5f; // the stage on which the band performs
      //if (y == 7 && (x < 2 || x > dimensions[0] - 3)) zpos = 0.67f * 0.5f; // steps
      //if (y == 8 && (x < 2 || x > dimensions[0] - 3)) zpos = 0.33f * 0.5f; // steps

      // safe raised hideouts
      //if ((y == 15 || y == 16) && (x > dimensions[0] / 2 - 2 && x < dimensions[0] / 2 + 1)) zpos = 1.0f * 0.5f;
      //if ((y == 25 || y == 26) && (x > dimensions[0] / 2 - 2 && x < dimensions[0] / 2 + 1)) zpos = 1.0f * 0.5f;

      bool isRoof = true; // roof or 'street level'?
      if (x < 2 || x >= dimensions[0] - 2 ||
          y < 2 || y >= dimensions[1] - 2) isRoof = false; // THE DEPTH!
      //if (y >= 15 && y < 17) isRoof = false; // depth between buildings

      float zpos = baseZ;
      if (!isRoof) zpos = -100;//0.5f;//-100.0f;

      Sprite *tileSprite = isoTileSprite2;
      if (isRoof) {
        tileSprite = ((x + y) % 2 == 0) ? isoTileSprite2 : isoTileSprite3;
        //tileSprite = isoTileSprite2;

        if (x == 2 || x == dimensions[0] - 3) {
          tileSprite = isoTileSprite1; // edges
          zpos += 0.25f;
        }
        else if (y % 8 == 0 || (y + 1) % 8 == 0) {
          tileSprite = isoTileSprite1; // building separators
          zpos += 0.25f;
        } else { // building
          //zpos += (sin(int(x / 3) * 12) + cos(int(y / 3) * 10)) * 0.3f;
        }
      }

      Object *object = new Object(&playState->GetWorldNode(), tileSprite);
      bool traversable = true;

      Tile *tile = new Tile(x, y, zpos, object, traversable);

      tile->object->spatial.z = 0;
      tile->object->spatial.orientation = playState->GetViewOrientation();
      tile->object->spatial.position = Vector2(x, y);
      tile->object->spatial.position -= tile->zPosition;// * 1.414f; // pow(2.0f, 0.5f);
      //tile.object->spatial.position = Vector2(x - y, (x + y) / 2.0f);
      //tile.object->spatial.scale = Vector2(1, 0.5f);//Vector2(1.5, 1.0) * 1.157f;
      //if (!funnyBool) {
      //}
      tile->object->spatial.scale = Vector2(1.0f, 0.5f);
      tile->object->spatial.scale *= 1.414f; // pow(2.0f, 0.5f);
      tile->object->spatial.scale *= 1.01f; // fixes edge artifacts

      //tile->object->spatial.position -= 0.5f;//tile->object->spatial.scale.coords[1]; // underside of sprite == 0, 0

      //if (x % 4 == 0 || y % 4 != 0) tile.object->GetSprite()->SetAlpha(0.5f);

      //tile.object->GetSprite()->SetAlpha(0.5f);
      tiles.push_back(tile);

    }
  }
*/

}

Stadium::~Stadium() {

  for (unsigned int i = 0; i < spaces.size(); i++) {
    delete spaces.at(i);
  }

  delete backgroundObject[0];
  delete backgroundSprite[0];
  delete backgroundObject[1];
  delete backgroundSprite[1];
  //delete backgroundPositioner;

/*
  for (unsigned int i = 0; i < tiles.size(); i++) {
    delete tiles.at(i)->object;
    delete tiles.at(i);
  }
  tiles.clear();
  delete isoTileSprite1;
  delete isoTileSprite2;
  delete isoTileSprite3;
*/

  /*
  delete testTreeObject1;
  delete testTreeObject2;
  delete testTreeObject3;
  */
}

void Stadium::OnStepPhysicsTime() {

  // load new spaces when needed
  GenerateTriggeredSpaces();

  //backgroundObject->spatial.position.coords[1] =

  /*
  float flowTilesPerBeat = 1.0f;
  if (stage->GetHumanCharactersRef().at(0)->GetModelView_const()->GetActiveMoveType() == e_MoveType_Hit) flowTilesPerBeat = 0.5f;
  if (stage->GetHumanCharactersRef().at(0)->GetPosition().coords[GetFlowAxis()] > GetFlowPosition().coords[GetFlowAxis()]) flowTilesPerBeat = 1.5f; else flowTilesPerBeat = 0.5f;
  this->flowVelocity = flowTilesPerBeat / (playState->GetCurrentMusic()->GetBeatDuration_ms() * 0.001f);
  this->flowMovement = flowDirection * flowVelocity;
  */

/*
  //pulsating flow
  this->flowVelocity = flowTilesPerBeat / (playState->GetCurrentMusic()->GetBeatDuration_ms() * 0.001f);
  this->flowVelocity *= 0.8f + 0.4f * (cos(playState->GetCurrentMusic()->GetBeatOffset() * 2.0f * pi) * 0.5f + 0.5f);
  this->flowMovement = flowDirection * flowVelocity;
*/

  this->flowPosition += this->flowMovement * playState->GetPhysicsTimeStep_ms() * 0.001f;

  for (unsigned int i = 0; i < spaces.size(); i++) {
    spaces.at(i)->OnStepPhysicsTime();
  }

}

/*
void Stadium::PostStepPhysicsTime() {

  this->flowPosition += this->flowMovement * playState->GetPhysicsTimeStep_ms() * 0.001f;

  for (unsigned int i = 0; i < spaces.size(); i++) {
    spaces.at(i)->PostStepPhysicsTime();
  }

}
*/

void Stadium::GetPhysicsObjects(std::vector<PhysicsObject*> &targetContainer) const {
  //targetContainer.insert(tiles.end(), tiles.begin(), tiles.end());
  //targetContainer.insert(std::end(tiles), std::begin(tiles), std::end(tiles));
  for (unsigned int i = 0; i < spaces.size(); i++) {
    if (spaces.at(i)->GetCenterPosition().GetDistance(GetFlowPosition()) <= _CULLDISTANCE) {
      spaces.at(i)->GetPhysicsObjects(targetContainer);
    }
  }
  //printf("%i\n", spaces.size());
}

/* better make specific functions for specific draw jobs (drawdetails etc), since most contents will be draws through the getphysicsobjects queue, and so draw() is confusing
void Stadium::Draw(const Viewport &viewport, const Camera &camera) const {
  for (unsigned int i = 0; i < spaces.size(); i++) {
    if (spaces.at(i)->GetCenterPosition().GetDistance(GetFlowPosition()) <= _CULLDISTANCE) {
      spaces.at(i)->Draw(viewport, camera);
    }
  }
}
*/

void Stadium::DrawBackLayer(const Viewport &viewport, const Camera &camera) const {
  for (unsigned int i = 0; i < spaces.size(); i++) {
    if (spaces.at(i)->GetCenterPosition().GetDistance(GetFlowPosition()) <= _CULLDISTANCE) {
      spaces.at(i)->DrawBackLayer(viewport, camera);
    }
  }
}

void Stadium::DrawFrontLayer(const Viewport &viewport, const Camera &camera) const {
  for (unsigned int i = 0; i < spaces.size(); i++) {
    if (spaces.at(i)->GetCenterPosition().GetDistance(GetFlowPosition()) <= _CULLDISTANCE) {
      spaces.at(i)->DrawFrontLayer(viewport, camera);
    }
  }
}

void Stadium::DrawBackground(const Viewport &viewport, const Camera &camera) const {
  backgroundObject[1]->Draw(viewport, camera);
  backgroundObject[0]->Draw(viewport, camera);
}

void Stadium::DrawLights() const {

  for (unsigned int i = 0; i < spaces.size(); i++) {
    if (spaces.at(i)->GetCenterPosition().GetDistance(GetFlowPosition()) <= _CULLDISTANCE) {
      spaces.at(i)->DrawLights();
    }
  }

  /*
  // test lights
  int steps = 6;
  float yPos = 10;
  for (float intensity = 0.0f; intensity <= 1.0f; intensity += (1.0f / (float)steps)) {
    yPos += (1 / (float)steps) * 25;
    //int y = 25 + intensity * 15;
    stage->DrawLight(Vector2(0, yPos), 0, 2.0f, intensity, intensity, intensity);
    stage->DrawLight(Vector2(dimensions[0] - 1, yPos), 0, 2.0f, 1, intensity, 0);
  }
  */
}

void Stadium::SetFlowTilesPerBeat(float tpb) {
  this->flowTilesPerBeat = tpb;
  //RecalibrateFlow();
  this->flowVelocity = flowTilesPerBeat / (playState->GetCurrentMusic()->GetBeatDuration_ms() * 0.001f);
  this->flowMovement = flowDirection * flowVelocity;
}

void Stadium::SetFlowPosition(const Vector2 &position) {
  this->flowPosition = position;
}

/*
void Stadium::RecalibrateFlow() {
  this->flowVelocity = flowTilesPerBeat / (playState->GetCurrentMusic()->GetBeatDuration_ms() * 0.001f);
  this->flowMovement = flowDirection * flowVelocity;
}
*/

// load new spaces when needed
void Stadium::GenerateTriggeredSpaces() {

  while (GetFlowPosition().coords[GetFlowAxis()] + _CULLDISTANCE > GetTriggerPosition().position.coords[GetFlowAxis()]) {

    Vector2 genPosition = GetTriggerPosition().position;

    // generate a space
    int spaceLength = int(round(Random(spaceGenConfig.minLength, spaceGenConfig.maxLength)));
    int spaceWidth = int(round(Random(spaceGenConfig.minWidth, spaceGenConfig.maxWidth) * 0.5f)) * 2; // makes sure this is in multiples of 2
    int widthVariationOffset = int(round(Random(-spaceGenConfig.widthVariation / 2, spaceGenConfig.widthVariation / 2)));
    RooftopSpace *space;
    if (GetFlowAxis() == 0) {
      space = new RooftopSpace(game, playState, stage, this, genPosition.coords[GetFlowAxis()], -spaceWidth / 2 + widthVariationOffset, spaceLength, spaceWidth, lastSpaceZ);
      //printf("%i, %i, %i, %i\n", currentDistance, -spaceWidth / 2 + widthVariationOffset, spaceLength, spaceWidth);
    } else {
      space = new RooftopSpace(game, playState, stage, this, -spaceWidth / 2 + widthVariationOffset, genPosition.coords[GetFlowAxis()], spaceWidth, spaceLength, lastSpaceZ);
      //printf("%i, %i, %i, %i\n", -spaceWidth / 2 + widthVariationOffset, currentDistance, spaceWidth, spaceLength);
    }

    genPosition.coords[GetFlowAxis()] += spaceLength;

    int separatorLength = int(Random(0, spaceGenConfig.maxSeparatorLength));
    genPosition.coords[GetFlowAxis()] += separatorLength;

    spaces.push_back(space);

    // populate!
    if (spacesLoaded > 1) { // first 2 roofs are devoid of entities
      std::vector<Vector2> spawnPositions = space->GetSpawnPositions();
      for (unsigned int i = 0; i < spawnPositions.size(); i++) {
        if (Random(0, 1) < 0.4f) {
          stage->SpawnAICharacter(e_Team_BadGuys, spawnPositions.at(i), e_AIControllerType_Grunt);
        }// else {
        //  stage->SpawnAICharacter(1, spawnPositions.at(i), e_AIControllerType_Thrower);
        //}
      }
    }

    /*
    if (spacesLoaded > 0) {
      Mixtape *mixtape = new Mixtape(game, playState, GetTriggerPosition().position, lastSpaceZ + 5.0f, 0, 0);
      playState->GetStage()->AddEntityOwnership(mixtape);
    }
    */

    lastSpaceZ -= round(Random(spaceGenConfig.minDescent, spaceGenConfig.maxDescent));

    UpdateTriggerPosition(genPosition, false);
    spacesLoaded++;
  }

  //const Tile *tile = playState->GetStage()->GetStadium()->GetTile(Vector2(2, 2));
  //if (!tile) printf("heh hier ook niet!\n");

}

const Vector2 Stadium::GetGoodGuySpawnPoint(int id) const {
  switch (id) {
    case 0:
      return GetFlowDirection() * 5;
      //return Vector2(5, 5);
      break;
    case 1:
      return Vector2(-2, -2) + GetFlowDirection() * 3;
      break;
    case 2:
      return Vector2(2, 2) + GetFlowDirection() * 3;
      break;
    default:
      return Vector2(Random(-3, 3), Random(-3, 3));
      break;
  }
}

const Space *Stadium::GetClosestSpace(const Vector2 &position) const {

  const Space *closestSpace = nullptr;
  float smallestDistance = 100000.0f;
  for (unsigned int i = 0; i < spaces.size(); i++) {
    float distance = spaces.at(i)->GetCenterPosition().GetDistance(position);
    if (distance < smallestDistance) {
      smallestDistance = distance;
      closestSpace = spaces.at(i);
    }
  }
  return closestSpace;

}

float Stadium::GetBaseZPosition(const Vector2 &position) const {
  const Tile *tile = GetTile(position);
  if (tile) return tile->GetZPosition(); else return 0.0f;
}

const Tile *Stadium::GetTile(int x, int y) const {

  for (unsigned int i = 0; i < spaces.size(); i++) {
    if (spaces.at(i)->Contains(x, y)) {
      return spaces.at(i)->GetTile(x, y);
      break;
    }
  }

  return nullptr;
}

const Tile *Stadium::GetTile(const Vector2 &position) const {
  return GetTile(int(round(position.coords[0])), int(round(position.coords[1])));
}

bool Stadium::IsTraversable(int x, int y) const {

  for (unsigned int i = 0; i < spaces.size(); i++) {
    if (spaces.at(i)->Contains(x, y)) {
      return spaces.at(i)->IsTraversable(x, y);
      break;
    }
  }

  return true; // empty space is traversable
}

bool Stadium::IsTraversable(const Vector2 &position) const {
  return IsTraversable(int(round(position.coords[0])), int(round(position.coords[1])));
}
