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

#include "slit.h"
#include "rect.h"
#include "mondrian.h"
#include "utils.h"
#include "fader.h"
#include "console.h"
#include <cmath>

using namespace std;

slit::slit () {
	boxes[0] = 0; edges[0] = -1;
	boxes[1] = 0; edges[1] = -1;
	start = end = mid = 0;
	type = INVALID;
	animt = INITIAL_OPEN_CLOSE_TIME;
	fdr = 0;
	select = 0;
	++ref;
}

slit::slit (rect** bxs, float x, float y, float sz, fader* _fdr) {
	// slit can be on 2 boxes only
	boxes [0]	=	bxs	[0]; // box 0
	boxes	[1]	=	bxs	[1]; // box 1
	rect* b0 = boxes [0];
	int e0 = b0->extents.get_edge_hit (x, y, mondrian::gutter2); // edge 0
	edges [0] = e0;
	edges [1] = (e0 + 2) % 4; // edge 1 = opposite edge on 2nd box
	int typs [] = {HORIZONTAL, VERTICAL, HORIZONTAL, VERTICAL};
	type = typs [e0];
	if (type == VERTICAL) mid = y; else mid = x;
	start = mid - sz;
	end = mid + sz;
	animt = INITIAL_OPEN_CLOSE_TIME;
	if (_fdr) {
		fdr = new fader ();
		fdr->copy (_fdr);
	} else fdr = 0;
	select = 0;
	++ref;
}

slit::~slit () {
	if (fdr) delete fdr;
	--ref;
}

int slit::is_too_small () {
	if (fdr) return 0;
	float sz = end - start;
	if (sz > MIN_SIZE) return 0; else return 1;
	return 0;
}

void slit::toggle_anim () {
	if (fdr) {
		start = anim_start;
		end = anim_end;
		calc_mid ();
		delete fdr;
		fdr = 0;
	} else {
		anim_start = start;
		anim_end = end;
		fdr = new fader (animt);
		fdr->restart ();
	}
}

void slit::eval_anim () {
	if (fdr) {
		fdr->eval ();
		start = (1 - fdr->amount) * anim_start + fdr->amount * anim_end;
		calc_mid ();
		if (fdr->reached) {
			fdr->restart ();
			fdr->flip = !fdr->flip;
		}
	}
}

rect* get_other_rect_of_slit (rect* ib, int e, float v, slit** os) {
	list<slit*>& slits = ib->slits[e];
	rect* or_ = 0;
	for (slit_iterator i = slits.begin (), j = slits.end (); i != j; ++i) {
		slit* si = *i;
		if (v >= si->start && v <= si->end) {
			if (si->boxes[0] == ib) or_ = si->boxes[1]; else or_ = si->boxes[0];
			*os = si;
			return or_;
		}
	}
	return 0;
}

rect* get_other_rect_of_slit (slit* s, rect* t) {
	if (s->boxes[0] == t) return s->boxes[1]; else return s->boxes[0];
}

int get_slit_lip (slit_lip_t& slit_lip, rect* R, int e, float v) {
	list<slit*>& slits = R->slits[e];
	static const int tol = 4;
	for (slit_iterator i = slits.begin (), j = slits.end (); i != j; ++i) {
		slit* si = *i;
		if (abs (v - si->start) < tol) {
			slit_lip.slitt = si;
			slit_lip.lip = &si->start;
			rect* R1 = get_other_rect_of_slit (si, R);
			box<float>& RE = R->extents, RE1 = R1->extents;
			float lows [rect::nedges] = {RE.left, RE.bottom, RE.left, RE.bottom};
			float lows1 [rect::nedges] = {RE1.left, RE1.bottom, RE1.left, RE1.bottom};
			float low = max (lows[e], lows1[e]);
			slit_iterator i1 = i; --i1;
			if (i1 != j) slit_lip.low = max ((*i1)->end, low); else slit_lip.low = low;
			slit_lip.high = si->end;
			return 1;
		} else if (abs (si->end - v) < tol) {
			slit_lip.slitt = si;
			slit_lip.lip = &si->end;
			slit_lip.low = si->start;
			rect* R1 = get_other_rect_of_slit (si, R);
			box<float>& RE = R->extents, RE1 = R1->extents;
			float highs [rect::nedges] = {RE.right, RE.top, RE.right, RE.top};
			float highs1 [rect::nedges] = {RE1.right, RE1.top, RE1.right, RE1.top};
			float high = min (highs[e], highs1[e]);
			slit_iterator i1 = i; ++i1;
			if (i1 != j) slit_lip.high = min ((*i1)->start, high); else slit_lip.high = high;
			return 1;
		}
	}
	return 0;
}

slit* slit_hit (rect* R, int e, float v) {
	list<slit*>& slits = R->slits[e];
	for (slit_iterator i = slits.begin (), j = slits.end (); i != j; ++i) {
		slit* si = *i;
		if (v >= si->start && v <= si->end) return si;
	}
	return 0;
}

slit* slit_hit (rect** bxs, float x, float y) {
	rect* b0 = bxs [0];
	int e0 = b0->extents.get_edge_hit (x, y, mondrian::gutter2);
	int e1 = (e0 + 2) % 4; 
	int e[2] = {e0, e1};
	float v[4] = {x, y, x, y};
	for (int i = 0; i < 2; ++i) {
		rect* b = bxs[i];
		int ei = e[i];
		slit* sh = slit_hit (b, ei, v[ei]);
		if (sh) return sh;
	}
	return 0;
}

void slit_lip_t::edit () {
	float delta = *cur - *prev;
	*lip += delta;
	clamp (low, *lip, high);
	slitt->calc_mid ();
}

void slit_lip_t::set_high (float h) {
	high = h;
	edit ();
}

void slit_lip_t::set_low (float l) {
	low = l;
	edit ();
}

void slit_lip_t::clear () {
	slitt = 0;
	lip = prev = cur = 0;
	low = high = 0;
}


slit_info::slit_info () { 
	x = y = half_size = 0; 
	anim = 0; 
}

void draw_slits (float start, float end, float level, int type, list<slit*>& slits, slit_drawer& slit_drawerr) {
	float last_end = start;
	if (type == slit::VERTICAL) {
		for (slit_iterator i = slits.begin (), j = slits.end (); i != j; ++i) {
			slit* si = *i;
			slit_drawerr.add (level, last_end);
			slit_drawerr.add (level, si->start);
			last_end = si->end;
		}
		slit_drawerr.add (level, last_end);
		slit_drawerr.add (level, end);
	} else {
		for (slit_iterator i = slits.begin (), j = slits.end (); i != j; ++i) {
			slit* si = *i;
			slit_drawerr.add (last_end, level);
			slit_drawerr.add (si->start, level);
			last_end = si->end;
		}
		slit_drawerr.add (last_end, level);
		slit_drawerr.add (end, level);
	}

}

slit_drawer::slit_drawer (int nlines) {
	int nverts = 2 * nlines;
	int nlocs = 2 * nverts;
	verts = new float [nlocs];
	last = nlocs - 2;
	cur = 0;
}

slit_drawer::~slit_drawer () {
	delete[] verts;
}

void slit_drawer::add (float x, float y) {
	if (cur < last) {
		verts[cur] = x;
		verts[cur+1] = y;
		cur += 2;
	} else {
		draw ();
	}
}

void slit_drawer::draw () {
	if (cur) {
		glVertexPointer (2, GL_FLOAT, 0, verts);
		glDrawArrays (GL_LINES, 0, cur / 2);
		cur = 0;
	}
}
