/*
* ball.cc
* DIN Is Noise is copyright (c) 2006-2024 Jagannathan Sampath
* DIN Is Noise is released under GNU Public License 2.0
* DIN Is Noise is released under GNU Public License 2.0
* For more information, please visit https://dinisnoise.org/
*/

#include "mondrian.h"
#include "console.h"
#include "utils.h"
#include "vector2d.h"
#include <utility>

using namespace std;

extern mondrian mondrian0;
extern const float PI_BY_180;
extern const float TWO_PI;
extern int TRAILSIZE;

ball::ball (int _type) : trail (TRAILSIZE) { 
  init (_type); 
}

void ball::init (int _type) {
	trig_what = NOTE;
  x = y = 0;
  R = 0;
  V = 0;
  vx = vy = 0;
  vm = vm_1 = vm_inf = 0;
  select = 0;
  frozen = 1;
	vol_mult = 1;
	mod = 0;
	attack_time = recent_attack_time;
	decay_time = recent_decay_time;
  pitch_mult = 1.0; // recent_pitch_mult;
	num_notes = 0;
	del = 0;
	auto_rotate = 0;
	dtheta = PI_BY_180;
	set_type (_type);
  ++ref;
}

void ball::clone_this (ball* C) {

	rnd<float> rd (0, TWO_PI);
	float rad = rd ();
	C->x = x + op_clone.offset * cos (rad);
	C->y = y + op_clone.offset * sin (rad);

	C->R = R;
	C->R->balls.push_back (C);

	C->V = V;
	C->vx = vx;
	C->vy = vy;
	C->vol_mult = vol_mult;
	C->mod = mod;
	C->attack_time = attack_time;
	C->decay_time = decay_time;
	C->pitch_mult = pitch_mult;
	C->auto_rotate = auto_rotate;
	C->dtheta = dtheta;
	C->set_type (type);
	C->op_turn = op_turn;
	C->op_speed = op_speed;
	C->op_teleport = op_teleport;
	C->op_clone = op_clone;
	C->op_clone.n = op_clone.max;
	if (op_clone.alarm.active) {
		if (op_clone.clone_can_clone)
			C->op_clone.alarm.start ();
		else
			C->op_clone.alarm.stop ();
	}
	C->op_transform = op_transform;
	C->frozen = frozen;
	C->trail = trail;
	C->trig_what = trig_what;
}

void ball::set_velocity (float dx, float dy) {
  V = unit_vector (vx, vy, dx, dy);
  if (V == 0) V = unit_vector (vx, vy, 1.0f, 1.0f);
	op_speed.max = max (op_speed.max, V);
	calc_velocity_slope ();
}

void ball::rotate_velocity (int dir) {
	rotate_vector (vx, vy, dir * dtheta); 
	calc_velocity_slope ();
}

void ball::calc_velocity_slope () {
	if (vx != 0) {
		vm = vy * 1.0f / vx;
		vm_1 = 1.0f / vm;
		vm_inf = 0;
	} else {
		vm = 0.0f;
		vm_1 = 0.0f;
		vm_inf = 1;
	}
}

void ball::on_edge_hit (int e1, int e2, int cl, float& v, float xry, float yrx, float elb, float ewh, pair<float, float>& invl, int& eh) {
	eh = 1;
	int walle [] = {e1, 0, e2};
	slit* S = 0;
	rect* RO = get_other_rect_of_slit (R, walle[cl+1], xry, &S);
	if (RO) { // ball in slit
		eh = 0; // edge hit didnt happen
		if (type == HEALER) mondrian0.remove_slit (S); // close slit
		R->erase (this);
		R = RO; // ball is now in other box
		R->balls.push_back (this);
	} else { // ball hit edge
		if (type == WRECKER) {
			if (mondrian0.slitting == mondrian::ANIMATE_SLIT) {
				mondrian0.fdr.restart ();
				mondrian0.add_remove_slit (x, y, &mondrian0.fdr); // make animated slit
			}
			else 
				mondrian0.add_remove_slit (x, y); // make slit
		}	
		v = -v; // flip component of velocity to rebound
		if (op_turn.alarm.active) op_turn.start (this);
		calc_velocity_slope ();
		float xy, t0, dt;
		xy = yrx;
		t0 = elb; 
		dt = ewh; 
		mondrian0.launch_note (this, xy, t0, dt, invl);
	}
}

void ball::update () {

	int edge_hit = 0;

  trail.add (x, y);

	if (!frozen) {

		float px = x, py = y; 

		// move the ball by a constant speed we found when user made the ball
		x += (V * vx);
		y += (V * vy);

		box<float>& e = R->extents; // get the box the ball is bouncing in
		box<float>& eroot = mondrian0.root->extents; // get the root box
		
		// check if the ball is still inside this box after move
		if (inbox (e, x, y)) { 
			// ball is still inside the box so theres nothing to do
		} else { // ball has hit a wall or ceiling of box
      check:
			int clx = clamp<float> (e.left, x, e.right);
			if (clx) { // hit wall
				if (vm_inf == 0) y = py + vm * (x - px); else y = py;
        float yb4 = y;
				if (clamp (e.bottom, y, e.top)) { // below floor or above ceiling
					y = yb4;
          goto check;
				}
				on_edge_hit (edge::LEFT, edge::RIGHT, clx, vx, y, x, eroot.left, eroot.width_1, R->vint, edge_hit);
			} else {
				int cly = clamp<float> (e.bottom, y, e.top);
				if (cly) { // hit floor or ceiling
					if (vm_inf == 0) x = px + vm_1 * (y - py); else x = px;
					on_edge_hit (edge::BOTTOM, edge::TOP, cly, vy, x, y, eroot.bottom, eroot.height_1, R->hint, edge_hit); 
				}
			}
		}
	} 
	if (auto_rotate && !edge_hit) rotate_velocity (auto_rotate); // auto_rotate = -1 is clockwise; auto_rotate = 1 is anti-clockwise
}


void ball::set_type (int T) {
	type = T;
	switch (type) {
		case ball::BOUNCER:
			color_using_modulation ();
			break;
		case ball::WRECKER:
			r = 1.0f; g = 0.25f; b = 0.25f;
			break;
		case ball::HEALER:
			r = 0.0f; g = 1.0f; b = 1.0f;
	}
}

void ball::color_using_modulation () {
	if (type != ball::BOUNCER) return;
	if (mod < 1) {
		r = g = b = 0.15f;
	} else if (mod > 1) {
		r = g = b = 1.0f;
	} else {
		r = g = b = 0.60f;
	}
}

void ball::print () {
  extern char BUFFER [];
  sprintf (BUFFER, "type = %s > %s, attack = %0.3f s, decay = %0.3f s, modulation = %0.3f", types_str [type], trigstr [trig_what], attack_time, decay_time, pitch_mult);
  cons << BUFFER << eol;
}

void ball::eval_ops () {
	ball_op* ops [ball_op::NUM_OPS] = {&op_turn, &op_speed, &op_teleport, &op_clone, &op_transform};
	for (int i = 0; i < ball_op::NUM_OPS; ++i) ops[i]->eval (this);
}
