#include <stdlib.h>
#include <math.h>
#include <vector>

#include "./defines.hpp"
#include "./vec3.hpp"
#include "./sphere.hpp"
#include "./sphereflake.hpp"

namespace raytracer
{

sphereflake::sphereflake(const vec3& pos, const vec3& firstUp, const double uncertLength, const double firstRadius, const material& mat): object(mat)
{
  this->pos_= pos;
  this->first_up_= firstUp;
  this->uncert_length_= uncertLength;
  this->first_radius_= firstRadius;
  
  //generate basic pattern
  float t = 1.0f;
  float a = t*12.0f*PI, daL = 2.0f*PI/6.0f, daU = 2.0f*PI/3.0f;

  // lower ring
  for (int i= 0; i< 6; i++)      
  {
    pattern_[i]= vec3(0, sin(a), cos(a));
    pattern_[i]= pattern_[i].normalize();
    a += daL;
  }

  // upper ring
  a -= daL / 2.0f;//shift by 30 degrees
  for (int i= 6; i< 9; i++)
  {
    pattern_[i]= vec3(sin(daL), sin(a), cos(a));
    pattern_[i]= pattern_[i].normalize();

    a += daU;
  }
};

bool sphereflake::intersects(const ray& ray, double& length, vec3& normal, material& material) const
{
  vec3 norm;
  double len= find_intersection_length(ray, this->pos_, this->first_radius_, this->first_up_, norm);

  if(len< ACCURACY) return false;
  length= len;
  material= *this->material_;
  normal= norm;
  return true;
};

double sphereflake::find_intersection_length(const ray& ray, const vec3& pPos, const double pRadius, const vec3& sDir, vec3& intersNormal) const
{
  double distOfCamera= ray.origin.add(pPos.negative()).length();
  double seenPRadius= pRadius/distOfCamera;

  //if the radius, seen from the camera is smaller than a pixel - do not check (bottom condition)
  if(seenPRadius< (uncert_length_*0.5f)) { return -1; }

  //construct children coordinates
  double oldRadius= pRadius, boundRadius= pRadius, radius= pRadius/3.0f;

  //generate coordinates of new spheres
  double dx = sDir.x, dy = sDir.y, dz = sDir.z;

  //find a vector int the plane perpendicular to s_dir
  vec3 b1;

  if(dx*dx < dy*dy && dx*dx < dz*dz)//dx is smallest  
    b1= vec3(0, dz, -dy);  
  else if(dy*dy < dz*dz)//dy is smallest
    b1 = vec3(dz, 0, -dx);  
  else//dz is smallest
    b1 = vec3(dy, -dx, 0);  

  //normalize the new vector
  b1= b1.normalize();

  //find another perpendicular vector by doing the cross product
  vec3 b2= sDir.cross(b1);

  //recalculate b1 using b2, and we have a new set of basis vectors
  b1= sDir.cross(b2);

  //first find the intersection length with the parent sphere
  double intrlen= sphere::find_sphere_intersection_length(ray, pPos, pRadius);

  bool initMinInters= true;
  if(intrlen== -1) initMinInters= false;  
  else intersNormal= sphere::get_sphere_normal_at(ray.origin.add(ray.direction.scale(intrlen)), pPos);//compute normal at contact - for lighting  

  vec3 lintersNormal;//normal at intersection, for children spheres
  for(int i= 0; i< 9; i++)
  {
    //get coordinate scalars from pattern
    double r1 = pattern_[i].x,
           r2 = pattern_[i].y,
           r3 = pattern_[i].z;

    //construct new child sphere dir for new coordinates
    double nx = r1*sDir.x + r2*b1.x + r3*b2.x,
           ny = r1*sDir.y + r2*b1.y + r3*b2.y,
           nz = r1*sDir.z + r2*b1.z + r3*b2.z;

    vec3 spdir(nx, ny, nz);//sphere dir vector
    double sprad= oldRadius+boundRadius;//calc bounding sphere offset
    vec3 boundcoord= pPos.add(spdir.scale(sprad));//coordinates for bounding sphere
    vec3 spcoord= pPos.add(spdir.scale(oldRadius+radius));//real sphere coordinates

    //find if there is an intersection with the bounding sphere, enveloping this child
    double lintrLen= sphere::find_sphere_intersection_length(ray, boundcoord, boundRadius);
    if(lintrLen>= 0)//if there is bounding inters, then recursively find the length of true intersect (this is performed on the child spheres of this child sphere)
    {
      //also calculate normal of intersection with true child sphere - this is usefull, if there are no other low level intersections
      lintersNormal = sphere::get_sphere_normal_at(ray.origin.add(ray.direction.scale(intrlen)), spcoord);
      lintrLen= this->find_intersection_length(ray, spcoord, radius, spdir, lintersNormal);
    }

    //find min intersection length, among all child spheres
    if(((initMinInters== false) || (intrlen> lintrLen)) && (lintrLen>= 0))
    {
      intrlen= lintrLen;
      intersNormal= lintersNormal;
      initMinInters= true;
    }
  }

  return intrlen;//finally return min intersection length
};

}
