/*
 * Copyright 2022-2024 Frederico de Oliveira Linhares
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "vector_3d.hpp"

#include <memory>

#include <glm/vec3.hpp>

#include <mruby/array.h>

#include "orientation_3d.hpp"

void
cg_free_vector_3d(mrb_state *mrb, void* obj)
{
  auto ptr = static_cast<std::shared_ptr<glm::vec3>*>(obj);

  ptr->~shared_ptr();
  mrb_free(mrb, ptr);
}

const struct mrb_data_type cg_vector_3d_type = {
  "CG_Vector3D", cg_free_vector_3d};

static mrb_value
cg_cVector3D_initialize(mrb_state *mrb, mrb_value self)
{
  mrb_float x = 0.0f;
  mrb_float y = 0.0f;
  mrb_float z = 0.0f;
  std::shared_ptr<glm::vec3> *ptr;

  mrb_get_args(mrb, "|fff", &x, &y, &z);
  ptr = (std::shared_ptr<glm::vec3>*)DATA_PTR(self);
  if(ptr) mrb_free(mrb, ptr);
  ptr = (std::shared_ptr<glm::vec3>*)mrb_malloc(
    mrb, sizeof(std::shared_ptr<glm::vec3>));

  new(ptr)std::shared_ptr<glm::vec3>(std::make_shared<glm::vec3>(x, y, z));

  mrb_data_init(self, ptr, &cg_vector_3d_type);
  return self;
}

mrb_value
cg_cVector3D_get_x(mrb_state *mrb, mrb_value self)
{
  std::shared_ptr<glm::vec3> *ptr =
    (std::shared_ptr<glm::vec3>*)DATA_PTR(self);

  return mrb_float_value(mrb, (*ptr)->x);
}

mrb_value
cg_cVector3D_get_y(mrb_state *mrb, mrb_value self)
{
  std::shared_ptr<glm::vec3> *ptr =
    (std::shared_ptr<glm::vec3>*)DATA_PTR(self);

  return mrb_float_value(mrb, (*ptr)->y);
}

mrb_value
cg_cVector3D_get_z(mrb_state *mrb, mrb_value self)
{
  std::shared_ptr<glm::vec3> *ptr =
    (std::shared_ptr<glm::vec3>*)DATA_PTR(self);

  return mrb_float_value(mrb, (*ptr)->z);
}

mrb_value
cg_cVector3D_get_xy(mrb_state *mrb, mrb_value self)
{
  std::shared_ptr<glm::vec3> *ptr =
    (std::shared_ptr<glm::vec3>*)DATA_PTR(self);

  mrb_value array = mrb_ary_new_capa(mrb, 2);
  mrb_ary_push(mrb, array, mrb_float_value(mrb, (*ptr)->x));
  mrb_ary_push(mrb, array, mrb_float_value(mrb, (*ptr)->y));

  return array;
}

mrb_value
cg_cVector3D_get_xz(mrb_state *mrb, mrb_value self)
{
  std::shared_ptr<glm::vec3> *ptr =
    (std::shared_ptr<glm::vec3>*)DATA_PTR(self);

  mrb_value array = mrb_ary_new_capa(mrb, 2);
  mrb_ary_push(mrb, array, mrb_float_value(mrb, (*ptr)->x));
  mrb_ary_push(mrb, array, mrb_float_value(mrb, (*ptr)->z));

  return array;
}

mrb_value
cg_cVector3D_get_yz(mrb_state *mrb, mrb_value self)
{
  std::shared_ptr<glm::vec3> *ptr =
    (std::shared_ptr<glm::vec3>*)DATA_PTR(self);

  mrb_value array = mrb_ary_new_capa(mrb, 2);
  mrb_ary_push(mrb, array, mrb_float_value(mrb, (*ptr)->y));
  mrb_ary_push(mrb, array, mrb_float_value(mrb, (*ptr)->z));

  return array;
}

mrb_value
cg_cVector3D_get_xyz(mrb_state *mrb, mrb_value self)
{
  std::shared_ptr<glm::vec3> *ptr =
    (std::shared_ptr<glm::vec3>*)DATA_PTR(self);

  mrb_value array = mrb_ary_new_capa(mrb, 3);
  mrb_ary_push(mrb, array, mrb_float_value(mrb, (*ptr)->x));
  mrb_ary_push(mrb, array, mrb_float_value(mrb, (*ptr)->y));
  mrb_ary_push(mrb, array, mrb_float_value(mrb, (*ptr)->z));

  return array;
}

static mrb_value
cg_cVector3D_set_x(mrb_state *mrb, mrb_value self)
{
  mrb_float x;
  std::shared_ptr<glm::vec3> *ptr =
    (std::shared_ptr<glm::vec3>*)DATA_PTR(self);

  mrb_get_args(mrb, "f", &x);
  (*ptr)->x = x;

  return self;
}

static mrb_value
cg_cVector3D_set_y(mrb_state *mrb, mrb_value self)
{
  mrb_float y;
  std::shared_ptr<glm::vec3> *ptr =
    (std::shared_ptr<glm::vec3>*)DATA_PTR(self);

  mrb_get_args(mrb, "f", &y);
  (*ptr)->y = y;

  return self;
}

static mrb_value
cg_cVector3D_set_z(mrb_state *mrb, mrb_value self)
{
  mrb_float z;
  std::shared_ptr<glm::vec3> *ptr =
    (std::shared_ptr<glm::vec3>*)DATA_PTR(self);

  mrb_get_args(mrb, "f", &z);
  (*ptr)->z = z;

  return self;
}

static mrb_value
cg_cVector3D_set_xy(mrb_state *mrb, mrb_value self)
{
  mrb_float x, y;
  std::shared_ptr<glm::vec3> *ptr =
    (std::shared_ptr<glm::vec3>*)DATA_PTR(self);

  mrb_get_args(mrb, "ff", &x, &y);
  (*ptr)->x = x;
  (*ptr)->y = y;

  return self;
}

static mrb_value
cg_cVector3D_set_xz(mrb_state *mrb, mrb_value self)
{
  mrb_float x, z;
  std::shared_ptr<glm::vec3> *ptr =
    (std::shared_ptr<glm::vec3>*)DATA_PTR(self);

  mrb_get_args(mrb, "ff", &x, &z);
  (*ptr)->x = x;
  (*ptr)->z = z;

  return self;
}

static mrb_value
cg_cVector3D_set_yz(mrb_state *mrb, mrb_value self)
{
  mrb_float y, z;
  std::shared_ptr<glm::vec3> *ptr =
    (std::shared_ptr<glm::vec3>*)DATA_PTR(self);

  mrb_get_args(mrb, "ff", &y, &z);
  (*ptr)->y = y;
  (*ptr)->z = z;

  return self;
}

static mrb_value
cg_cVector3D_set_xyz(mrb_state *mrb, mrb_value self)
{
  mrb_float x, y, z;
  std::shared_ptr<glm::vec3> *ptr =
    (std::shared_ptr<glm::vec3>*)DATA_PTR(self);

  mrb_get_args(mrb, "fff", &x, &y, &z);
  (*ptr)->x = x;
  (*ptr)->y = y;
  (*ptr)->z = z;

  return self;
}

static mrb_value
cg_cVector3D_translate(mrb_state *mrb, mrb_value self)
{
	auto *ptr = (std::shared_ptr<glm::vec3>*)DATA_PTR(self);
	std::shared_ptr<glm::vec3> *direction;
	std::shared_ptr<glm::quat> *orientation;

  mrb_get_args(
    mrb, "dd", &direction, &cg_vector_3d_type,
    &orientation, &cg_orientation_3d_type);

	auto rotated_direction = **orientation * **direction;

	**ptr += rotated_direction;

  return self;
}

void
cg_vector_3d_init(mrb_state *mrb)
{
  struct RClass *cg_m, *cg_cVector3D;

  cg_m = mrb_module_get(mrb, "CandyGear");
  cg_cVector3D = mrb_define_class_under(
    mrb, cg_m, "Vector3D", mrb->object_class);
  MRB_SET_INSTANCE_TT(cg_cVector3D, MRB_TT_DATA);
  mrb_define_method(
    mrb, cg_cVector3D, "initialize", cg_cVector3D_initialize,
    MRB_ARGS_NONE()|MRB_ARGS_OPT(3));

  mrb_define_method(
    mrb, cg_cVector3D, "x", cg_cVector3D_get_x, MRB_ARGS_NONE());
  mrb_define_method(
    mrb, cg_cVector3D, "y", cg_cVector3D_get_y, MRB_ARGS_NONE());
  mrb_define_method(
    mrb, cg_cVector3D, "z", cg_cVector3D_get_z, MRB_ARGS_NONE());

  mrb_define_method(
    mrb, cg_cVector3D, "xy", cg_cVector3D_get_xy, MRB_ARGS_NONE());
  mrb_define_method(
    mrb, cg_cVector3D, "xz", cg_cVector3D_get_xz, MRB_ARGS_NONE());
  mrb_define_method(
    mrb, cg_cVector3D, "yz", cg_cVector3D_get_yz, MRB_ARGS_NONE());

  mrb_define_method(
    mrb, cg_cVector3D, "xyz", cg_cVector3D_get_xyz, MRB_ARGS_NONE());

  mrb_define_method(
    mrb, cg_cVector3D, "x=", cg_cVector3D_set_x, MRB_ARGS_REQ(1));
  mrb_define_method(
    mrb, cg_cVector3D, "y=", cg_cVector3D_set_y, MRB_ARGS_REQ(1));
  mrb_define_method(
    mrb, cg_cVector3D, "z=", cg_cVector3D_set_z, MRB_ARGS_REQ(1));

  mrb_define_method(
    mrb, cg_cVector3D, "set_xy", cg_cVector3D_set_xy, MRB_ARGS_REQ(2));
  mrb_define_method(
    mrb, cg_cVector3D, "set_xz", cg_cVector3D_set_xz, MRB_ARGS_REQ(2));
  mrb_define_method(
    mrb, cg_cVector3D, "set_yz", cg_cVector3D_set_yz, MRB_ARGS_REQ(2));

  mrb_define_method(
    mrb, cg_cVector3D, "set_xyz", cg_cVector3D_set_xyz, MRB_ARGS_REQ(3));

  mrb_define_method(
    mrb, cg_cVector3D, "translate", cg_cVector3D_translate, MRB_ARGS_REQ(2));
}