#define SAF_PROGRAM_NAME "tpe1"
#define SAF_PLATFORM_POKITTO
#define SAF_SETTING_ENABLE_SOUND 0
#define SAF_SETTING_ENABLE_SAVES 0

#define S3L_RESOLUTION_X SAF_SCREEN_WIDTH
#define S3L_RESOLUTION_Y SAF_SCREEN_HEIGHT
#define S3L_PIXEL_FUNCTION s3l_drawPixel

#include "saf.h"

#define S3L_Z_BUFFER 0
#define S3L_SORT 1
#define S3L_FLAT 1
#define S3L_NEAR_CROSS_STRATEGY 1

#define MAP_BOUND 15000

//#define TPE_RESHAPE_ITERATIONS 2
#define TPE_APPROXIMATE_NET_SPEED 1

#define ACCELERATION (TPE_F / 13)
#define TURN_RATE (3 * TPE_F / 4)
#define TURN_FRICTION (3 * TPE_F / 4)
#define FORW_FRICTION (TPE_F / 14)

#include "../tinyphysicsengine.h"
#include "../programs/small3dlib.h"
#include "carModel.h"

TPE_World tpe_world;
TPE_Joint tpe_joints[5];
TPE_Connection tpe_connections[10];
TPE_Body tpe_body;

S3L_Scene s3l_scene;

uint8_t debugDraw = 0;

#define PYRAMID_VERTICES 5
const S3L_Unit pyramidVertices[PYRAMID_VERTICES * 3] =
{
  512,0,-512,
  512,0,512,
  -512,0,512,
  -512,0,-512,
  0,512,0
};

#define PYRAMID_TRIANGLES 4
const S3L_Index pyramidTriangles[PYRAMID_TRIANGLES * 3] =
{
  0,1,4,
  1,2,4,
  2,3,4,
  3,0,4
};

#define RAMP_VERTICES 4
const S3L_Unit rampVertices[RAMP_VERTICES * 3] =
{
  512,0,-512,
  512,512,512,
  -512,512,512,
  -512,0,-512
};

#define RAMP_TRIANGLES 4
const S3L_Index rampTriangles[RAMP_TRIANGLES * 3] =
{
  0,1,2,
  2,3,0,
  1,0,2,
  3,2,0
};

S3L_Model3D models[4];

void tpe_debugDrawPixel(uint16_t x, uint16_t y, uint8_t color)
{
  SAF_drawPixel(x,y,(color + 2) * 32 + color);
}

const TPE_Unit rampPoits[6] = { 0,0, -2400,1400, -2400,0 };

TPE_Vec3 tpe_environmentDistance(TPE_Vec3 p, TPE_Unit maxD)
{
  TPE_ENV_START (TPE_envGround(p,0), p)
  TPE_ENV_NEXT (TPE_envSphere(p,TPE_vec3(0,0,-4000),1000), p)
  TPE_ENV_NEXT( TPE_envAATriPrism(p,TPE_vec3(-6500,0,0),rampPoits,5000,2),p )
  TPE_ENV_END
}

void s3l_drawPixel(S3L_PixelInfo *p)
{
  SAF_drawPixel(p->x,p->y,(p->triangleIndex + 100) % 256);
}

uint8_t steering = 0;
uint8_t jointCollisions;
uint8_t jointCollisionsPrev;
TPE_Vec3 carForw, carSide, carUp, carPos, carRot;

uint8_t collisionCallback(uint16_t b1, uint16_t j1, uint16_t b2, uint16_t j2,
  TPE_Vec3 p)
{
  if (b1 == 0 && b1 == b2 && j1 < 4)
    jointCollisions |= 0x01 << j1;

  return 1;
}

void carReset(void)
{
  TPE_bodyStop(&tpe_body);

  tpe_joints[3].position = TPE_vec3(0,3000,10000);
  tpe_joints[2].position = TPE_vec3(512,3000,9500);
  tpe_joints[1].position = TPE_vec3(-512,3000,9500);
  tpe_joints[0].position = TPE_vec3(512,3000,10500);
  tpe_joints[4].position = TPE_vec3(512,3500,10500);

  s3l_scene.camera.transform.translation.z = tpe_body.joints[4].position.z + 3000;
}

void SAF_init(void)
{
  carModelInit();

  models[0] = carModel;

  S3L_model3DInit(pyramidVertices,PYRAMID_VERTICES,pyramidTriangles,PYRAMID_TRIANGLES,&models[1]);

  models[1].transform.translation.z = -4000;
  models[1].transform.scale.x = 1300;
  models[1].transform.scale.y = models[1].transform.scale.x;
  models[1].transform.scale.z = models[1].transform.scale.x;

  S3L_model3DInit(rampVertices,RAMP_VERTICES,rampTriangles,RAMP_TRIANGLES,&models[2]);

  models[2].transform.translation.x = -6500;
  models[2].transform.translation.z = -800;
  models[2].transform.scale.x = 2500;
  models[2].transform.scale.y = 1300;
  models[2].transform.scale.z = 1200;
  models[2].transform.rotation.y = 255;

  S3L_sceneInit(models,3,&s3l_scene);

  s3l_scene.camera.transform.rotation.x -= TPE_F / 14;

  TPE_makeCenterRectFull(tpe_joints,tpe_connections,1000,1800,400);

  tpe_joints[4].position.y += 600;
  tpe_joints[4].sizeDivided *= 4;
  tpe_joints[4].sizeDivided /= 3;

  TPE_bodyInit(&tpe_body,tpe_joints,5,tpe_connections,10,TPE_F / 2);

  tpe_body.elasticity = TPE_F / 100; 
  tpe_body.friction = FORW_FRICTION;
  tpe_body.flags |= TPE_BODY_FLAG_ALWAYS_ACTIVE;

  carReset();

  TPE_worldInit(&tpe_world,&tpe_body,1,tpe_environmentDistance);
  tpe_world.collisionCallback = collisionCallback;
}

uint8_t SAF_loop(void)
{
  SAF_clearScreen(SAF_COLOR_GRAY);

  for (uint8_t i = 0; i < 4; ++i)
  {
    S3L_Vec4 boundPoint, screenPoint;
    S3L_vec4Set(&boundPoint,
      i % 2 == 0 ? -1 * MAP_BOUND : MAP_BOUND,0,
      i / 2 == 0 ? -1 * MAP_BOUND : MAP_BOUND,1024);

    S3L_project3DPointToScreen(boundPoint,s3l_scene.camera,&screenPoint);

    if (screenPoint.w != 0)
      SAF_drawPixel(screenPoint.x,screenPoint.y,SAF_COLOR_RED);
  }
    
  jointCollisions = 0;

  if (SAF_buttonPressed(SAF_BUTTON_RIGHT))
    steering = 1;
  else if (SAF_buttonPressed(SAF_BUTTON_LEFT))
    steering = 2;
  else
    steering = 0;

  TPE_worldStep(&tpe_world);

  TPE_bodyApplyGravity(&tpe_body,TPE_F / 50);
    
  carPos = TPE_bodyGetCenterOfMass(&tpe_body); 

  carForw = TPE_vec3Normalized(
    TPE_vec3Minus(tpe_body.joints[2].position,tpe_body.joints[0].position));

  carSide = TPE_vec3Normalized(
      TPE_vec3Minus(tpe_body.joints[1].position,tpe_body.joints[0].position));

  carUp = TPE_vec3Cross(carForw,carSide);
    
  for (int i = 0; i < 4; ++i)
    if (jointCollisions & (0x01 << i))
    {
      TPE_Vec3 jv = TPE_vec3(
          tpe_body.joints[i].velocity[0],   
          tpe_body.joints[i].velocity[1],   
          tpe_body.joints[i].velocity[2]);   

      TPE_Vec3 ja = carSide;

      if (i >= 2 && steering)
      {
        if (steering == 2)
          ja = TPE_vec3Plus(TPE_vec3Times(carForw,TURN_RATE),carSide);
        else
          ja = TPE_vec3Minus(TPE_vec3Times(carForw,TURN_RATE),carSide);

        ja = TPE_vec3Normalized(ja);
      }

      TPE_Vec3 fric = TPE_vec3Times(ja,(TPE_vec3Dot(ja,jv) * TURN_FRICTION)
        / TPE_F);

      jv = TPE_vec3Minus(jv,fric);

      tpe_body.joints[i].velocity[0] = jv.x;
      tpe_body.joints[i].velocity[1] = jv.y;
      tpe_body.joints[i].velocity[2] = jv.z;
    }
    
  if (TPE_vec3Dot(carUp,TPE_vec3Minus(tpe_body.joints[4].position,
      tpe_body.joints[0].position)) < 0)
    tpe_body.joints[4].position = TPE_vec3Plus(TPE_vec3Times(carUp,300),
      tpe_body.joints[0].position);
    
  if ((jointCollisions | jointCollisionsPrev) & 0x03) // back wheels on ground?
  {
    if (SAF_buttonPressed(SAF_BUTTON_UP))
    {
      tpe_body.joints[0].velocity[0] += (carForw.x * ACCELERATION) / TPE_F;
      tpe_body.joints[0].velocity[1] += (carForw.y * ACCELERATION) / TPE_F;
      tpe_body.joints[0].velocity[2] += (carForw.z * ACCELERATION) / TPE_F;
      tpe_body.joints[1].velocity[0] += (carForw.x * ACCELERATION) / TPE_F;
      tpe_body.joints[1].velocity[1] += (carForw.y * ACCELERATION) / TPE_F;
      tpe_body.joints[1].velocity[2] += (carForw.z * ACCELERATION) / TPE_F;
    }
    else if (SAF_buttonPressed(SAF_BUTTON_DOWN))
    {
      tpe_body.joints[0].velocity[0] -= (carForw.x * ACCELERATION) / TPE_F;
      tpe_body.joints[0].velocity[1] -= (carForw.y * ACCELERATION) / TPE_F;
      tpe_body.joints[0].velocity[2] -= (carForw.z * ACCELERATION) / TPE_F;
      tpe_body.joints[1].velocity[0] -= (carForw.x * ACCELERATION) / TPE_F;
      tpe_body.joints[1].velocity[1] -= (carForw.y * ACCELERATION) / TPE_F;
      tpe_body.joints[1].velocity[2] -= (carForw.z * ACCELERATION) / TPE_F;
    }
  }
    
  if (SAF_buttonJustPressed(SAF_BUTTON_B))
    carReset();

  jointCollisionsPrev = jointCollisions;

  carRot = TPE_bodyGetRotation(&tpe_body,0,2,1);

  S3L_newFrame();

  models[0].transform.translation.x = carPos.x;
  models[0].transform.translation.y = carPos.y;
  models[0].transform.translation.z = carPos.z;

  models[0].transform.rotation.x = carRot.x;
  models[0].transform.rotation.y = carRot.y;
  models[0].transform.rotation.z = carRot.z;

  if (carPos.x > MAP_BOUND)
  {
    TPE_bodyMoveBy(&tpe_body,TPE_vec3(-2 * MAP_BOUND,0,0));
    s3l_scene.camera.transform.translation.x -= 2 * MAP_BOUND;
  }
  else if (carPos.x < -1 * MAP_BOUND)
  {
    TPE_bodyMoveBy(&tpe_body,TPE_vec3(2 * MAP_BOUND,0,0));
    s3l_scene.camera.transform.translation.x += 2 * MAP_BOUND;
  }

  if (carPos.z > MAP_BOUND)
  {
    TPE_bodyMoveBy(&tpe_body,TPE_vec3(0,0,-2 * MAP_BOUND));
    s3l_scene.camera.transform.translation.z -= 2 * MAP_BOUND;
  }
  else if (carPos.z < -1 * MAP_BOUND)
  {
    TPE_bodyMoveBy(&tpe_body,TPE_vec3(0,0,2 * MAP_BOUND));
    s3l_scene.camera.transform.translation.z += 2 * MAP_BOUND;
  }

  S3L_drawScene(s3l_scene);

  if (SAF_buttonJustPressed(SAF_BUTTON_C))
    debugDraw = !debugDraw;

  if (debugDraw)
    TPE_worldDebugDraw(&tpe_world,tpe_debugDrawPixel,
      TPE_vec3(
        s3l_scene.camera.transform.translation.x,
        s3l_scene.camera.transform.translation.y,
        s3l_scene.camera.transform.translation.z),
      TPE_vec3(s3l_scene.camera.transform.rotation.x,s3l_scene.camera.transform.rotation.y,0),
      TPE_vec3(SAF_SCREEN_WIDTH,SAF_SCREEN_HEIGHT,TPE_F),4,TPE_F);

  s3l_scene.camera.transform.translation.y = carPos.y + 900;

  TPE_Vec3 v = TPE_vec3KeepWithinDistanceBand(
    TPE_vec3(
        s3l_scene.camera.transform.translation.x,
        s3l_scene.camera.transform.translation.y,
        s3l_scene.camera.transform.translation.z
      ),tpe_body.joints[4].position,2 * TPE_F,4 * TPE_F);

  s3l_scene.camera.transform.translation.x = v.x;
  s3l_scene.camera.transform.translation.y = v.y;
  s3l_scene.camera.transform.translation.z = v.z;

  S3L_Vec4 toCar;

  toCar.x = carPos.x - s3l_scene.camera.transform.translation.x;
  toCar.y = carPos.y - s3l_scene.camera.transform.translation.y;
  toCar.z = carPos.z - s3l_scene.camera.transform.translation.z;
  toCar.w = 0;

  TPE_Unit angleDiff = s3l_scene.camera.transform.rotation.y -
    (TPE_vec2Angle(toCar.x,toCar.z) - 128);

  s3l_scene.camera.transform.rotation.y -= 
    (angleDiff < 100 && angleDiff > -100) ? angleDiff / 2 : angleDiff;

  return 1;
}
