/*
 *  Copyright 1994-2022 Olivier Girondel
 *
 *  This file is part of lebiniou.
 *
 *  lebiniou is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  lebiniou is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with lebiniou. If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef __BINIOU_PARAMS3D_H
#define __BINIOU_PARAMS3D_H

#include <jansson.h>
#include "point3d.h"
#include "buffer_8bits.h"


enum Axes { X_AXIS = 0, Y_AXIS, Z_AXIS };

typedef enum Boudaries {
  BOUNDARY_NONE,
  BOUNDARY_CUBE,
  BOUNDARY_SPHERE_DOTS,
  BOUNDARY_SPHERE_WIREFRAME,
  NB_BOUNDARIES
} Boundaries_t;

#define DIST 4

typedef struct Params3d_s {
  double  scale_factor0;
  double  scale_factor;
  int8_t  scale_factor_coeff;

  double  rotate_amount;
  int8_t  rotate_factor[3];
  uint8_t rotation_factor;

  Boundaries_t boundary; /* for 3D objects */

  /* should only be modified by update_ functions ! */
  float   rotations[3];
  float   Cos[3];
  float   Sin[3];

  /* handle drag/drop 3D rotations */
  int     xs, ys;
  int     xe, ye;

#ifdef WITH_GL
  /* OpenGL */
  float   gl_rotations[3];
  int     gl_xs, gl_ys;
  int     gl_xe, gl_ye;
  float   gl_fov;
#endif
} Params3d_t;


uint8_t Params3d_is_rotating(const Params3d_t *);

void Params3d_set_defaults(Params3d_t *);
void Params3d_init(Params3d_t *);
void Params3d_randomize(Params3d_t *);
void update_x(Params3d_t *);
void update_y(Params3d_t *);
void update_z(Params3d_t *);


static inline void
update(Params3d_t *p)
{
  update_x(p);
  update_y(p);
  update_z(p);
}


static inline void
zero_3d(Params3d_t *p)
{
  for (uint8_t i = X_AXIS; i <= Z_AXIS; i++) {
    p->rotations[i] = 0;
    p->rotate_factor[i] = 0;
  }
  update(p);
}


static inline void
spin_x(const Params3d_t *params3d, Point3d_t *p)
{
  /* Spin x */
  float tmp1, tmp2;

  tmp1 = p->pos.y * params3d->Cos[X_AXIS] + p->pos.z * params3d->Sin[X_AXIS];
  tmp2 = p->pos.z * params3d->Cos[X_AXIS] - p->pos.y * params3d->Sin[X_AXIS];

  p->pos.y = tmp1;
  p->pos.z = tmp2;
}


static inline void
spin_y(const Params3d_t *params3d, Point3d_t *p)
{
  /* Spin y */
  float tmp1, tmp2;

  tmp1 = p->pos.x * params3d->Cos[Y_AXIS] - p->pos.z * params3d->Sin[Y_AXIS];
  tmp2 = p->pos.x * params3d->Sin[Y_AXIS] + p->pos.z * params3d->Cos[Y_AXIS];

  p->pos.x = tmp1;
  p->pos.z = tmp2;
}


static inline void
spin_z(const Params3d_t *params3d, Point3d_t *p)
{
  /* Spin z */
  float tmp1, tmp2;

  tmp1 = p->pos.x * params3d->Cos[Z_AXIS] + p->pos.y * params3d->Sin[Z_AXIS];
  tmp2 = p->pos.y * params3d->Cos[Z_AXIS] - p->pos.x * params3d->Sin[Z_AXIS];

  p->pos.x = tmp1;
  p->pos.y = tmp2;
}


static inline void
spin(const Params3d_t *params3d, Point3d_t *p)
{
  spin_z(params3d, p);
  spin_x(params3d, p);
  spin_y(params3d, p);
}


static inline Point2d_t
projection_perspective(const Params3d_t *params3d, const Point3d_t *p)
{
  Point2d_t P;
  Point3d_t q = *p;

  spin(params3d, &q);

  P.x = DIST * q.pos.x / (DIST + q.pos.z);
  P.y = DIST * q.pos.y / (DIST + q.pos.z);

  return P;
}


static inline Point2d_t
pixel_ecran(const Params3d_t *params3d, const Point2d_t *p)
{
  Point2d_t P;

  P.x = (short)(params3d->scale_factor * p->x + CENTERX);
  P.y = (short)(params3d->scale_factor * p->y + CENTERY);

  return P;
}


static inline void
set_pixel_3d(const Params3d_t *params3d, Buffer8_t *b, const Point3d_t *p, const guchar color)
{
  Point2d_t pp;
  Point2d_t a;

  pp = projection_perspective(params3d, p);
  a = pixel_ecran(params3d, &pp);

  set_pixel(b, (short)a.x, (short)a.y, color);
}


// Boundaries
Boundaries_t Params3d_str2boundary(const char *);
const char *Params3d_boundary2str(const Boundaries_t);
void draw_cube_3d(const Params3d_t *, Buffer8_t *, const Pixel_t);
void draw_sphere_3d(const Params3d_t *, Buffer8_t *, const Pixel_t);
void draw_sphere_wireframe_3d(const Params3d_t *, Buffer8_t *, const Pixel_t);
void draw_line_3d(const Params3d_t *, Buffer8_t *, const Point3d_t *, const Point3d_t *, const Pixel_t);

void Params3d_rotate(Params3d_t *);
#ifdef WITH_GL
void Params3d_rotate_GL(Params3d_t *);
#endif
void Params3d_change_rotations(Params3d_t *);

void Params3d_from_json(Params3d_t *, const json_t *);
json_t *Params3d_to_json(const Params3d_t *);

void Params3d_zoom_in(Params3d_t *);
void Params3d_zoom_out(Params3d_t *);

void Params3d_set_rotate_factor(Params3d_t *, const enum Axes, const int8_t);

#endif /* __BINIOU_PARAMS3D_H */
