/*
* levels.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 "main.h"
#include "levels.h"
#include "viewwin.h"
#include "input.h"
#include "console.h"
#include "utils.h"
#include "font.h"
#include "log.h"

using namespace std;

extern string user_data_dir;
extern int mousex, mousey, wheel, mouseyy, prev_mousey;
extern char BUFFER [];
extern int line_height;

levels::levels (const string& s) {
  name = s;
	editing = lmb_clicked = paint = nsel = 0;
	selection = 0;
  chgl = 0;
  shftl = 0;
  paintl = 0;
  load ();

  if (editable) {
    const char* bt [] = {"Wrap", "Slide", "<<", ">>", "All", "None", "Invert", "+", "-", "x"};
    button* b[] = {&cbwrap, &bslide, &blshift, &brshift, &ball, &bnone, &binvert, &plus, &minus, &cross};
    click_listener* cl [] = {&cbwrap, &sll, &lsl, &rsl, &alll, &nonl, &invl, &pll, &mil, &crol};
    for (int i = 0; i < 10; ++i) {
      button* pbi = b[i];
      click_listener* pcl = cl[i];
      pbi->set_text (bt[i]);
      pbi->set_listener (pcl);
      pcl->data = this;
      this->add_child (pbi);
    }

    this->add_child (&szr);

    MOVE (szr);
    szr.movlis = this;

    blshift.click_repeat = brshift.click_repeat = 1;

    bml.data = this;
  }
}

levels::~levels () {
  save ();
  removeallbookmarks ();
	if (selection) delete[] selection;
}

void levels::load () {

  string ignore;
  int left, bottom;

  ifstream file ((user_data_dir + name).c_str(), ios::in);
  file >> ignore >> n;
  last = n - 1;
  file >> ignore >> left >> bottom;
  file >> ignore >> elem;
  file >> ignore >> height;
  file >> ignore >> editable;
  file >> ignore >> saveable;

  int w;
  file >> ignore >> w;
  cbwrap.set_state (w,0);

	a = 0.7;
  a0 = a / 4;
  extents (left, bottom, left + n * elem, bottom + height);
  set_pos (left, bottom);
  
  values.resize (n);
  heights.resize (n);

	selection = new bool [n];
	sel_sz = sizeof (bool) * n;
	memset (selection, 0, sel_sz);

  if (saveable) {
    int savings;
    file >> ignore >> savings;
    for (int i = 0; i < savings; ++i) {
      float h; file >> h >> selection[i];
      heights[i] = h;
      values[i] =  h * extents.height_1;
    }
		file >> nsel;
  }

  int nb;
  file >> ignore >> nb;
  if (nb) {
    bmk.resize (nb);
    for (int i = 0, y = minus.extents.bottom - state_button::SIZE2; i < nb; ++i) {
      bookmark* b = new bookmark ();
      b->set_pos (minus.extents.left, y);
      y -= state_button::SIZE2;
      b->set_listener (&bml);
      this->add_child (b);
      bmk[i] = b;
      int nid;
      file >> nid;
      vector<int>& ids = b->ids;
      ids.resize (nid);
      for (int m = 0; m < nid; ++m) file >> ids[m];
    }
  }

  editing = 0;

}

void levels::save () {
  ofstream file ((user_data_dir+name).c_str(), ios::out);
  if (file) {
    file << "num_levels " << n << endl;
    file << "lower_corner " << extents.left << spc << extents.bottom << endl;
    file << "element_width " << elem << endl;
    file << "height " << height << endl;
    file << "editable " << editable << endl;
    file << "saveable " << saveable << endl;
		file << "wrap " << cbwrap.state << endl;
    if (saveable) {
      file << "savings " << n << spc;
      for (int i = 0; i < n; ++i) file << heights[i] << spc << selection[i] << spc;
			file << nsel << endl;
    } else {
      file << "savings 0" << endl;
    }

    int j = bmk.size ();
    file << "bookmarks " << j << spc;
    if (j) {
      for (int i = 0; i < j; ++i) {
        bookmark* bi = bmk[i];
        vector<int>& ids = bi->ids;
        int n = ids.size ();
        file << n << spc;
        for (int m = 0; m < n; ++m) file << ids[m] << spc;
      }
    }
  } else dlog << "!!! couldnt save levels !!!" << endl;
}

int levels::stop_editing () {
	if (editing == FINISH) {
		editing = 0;
		return 1;
	}
	return 0;
}

int levels::handle_input () {

	prev_mousey = mousey;

  widget::handle_input ();

	int hne = hover && !editing;
	if (hne) calc_lev (); 

  if (keypressed (SDLK_f)) {
    paint = !paint;
    if (paintl) paintl->paint (*this);
  }

	if (editable) {

    for (int i = 0, j = children.size(); i < j; ++i) {
      if (children[i]->handle_input ()) return 1;
    }

		if (lmb) {
			if (lmb_clicked == 0) {
				if (stop_editing () == 0) {
					if (hne) {
						if (SHIFT) {
							bool& k = selection [lev];
							k = !k;
							if (k) ++nsel; else --nsel;
						} else {
							editing = STARTED;
							widget::focus = this;
						}
					}
				}
			}
			lmb_clicked = 1;
		} else { 

			lmb_clicked = 0;
			if (editing) {

				if (wheel) {
					mousey -= wheel; 
					warp_mouse (mousex, mousey);
				}

				if (paint) calc_lev ();

				calc_hgt_val ();

				int dh = set (lev, val, hgt);
				int uml = 0;
				if (selection[lev] && !SHIFT ) uml = update_mul_lev (dh);
				if (chgl && (dh || uml)) chgl->changed (*this);

				editing = FINISH;

			} else {
				if (widget::focus == this) widget::focus = 0;
			}
		}
	}

  return 1;

}

int levels::update_mul_lev (int dh) {
	float dv = dh * extents.height_1;
	int ret = 0;
	for (int i = 0; i < n; ++i) {
		if ((i != lev) && selection[i]) {
			int& hi = heights[i];
			float& vi = values[i];
			int hji = hi + dh; 
			float vji = vi + dv;

			if (hji < 0) hji = 0; else if (hji > extents.height) hji = extents.height;
			if (vji < 0.0f) vji = 0.0f; else if (vji > 1.0f) vji = 1.0f;
			hi = hji;
			vi = vji;
			ret = 1;
		}
	}
	return ret;
}

void levels::calc_lev () {
  lev =  (mousex - extents.left) / elem;
  if (lev > last || lev < 0) lev = -1;
}

void levels::calc_hgt_val () {
  hgt = mouseyy - extents.bottom;
	clamp<int> (0, hgt, extents.height);
  val = hgt * extents.height_1;
}

void levels::clear_hgt_val () {
  for (int i = 0; i < n; ++i) values[i] = heights[i] = 0;
}

void levels::draw () {

  if (editing) {
    glColor3f (.5f, .5f, .5f);
    int ybh = extents.bottom + hgt;
    glBegin (GL_LINES);
      glVertex2i (extents.left, ybh);
      glVertex2i (extents.right, ybh);
    glEnd ();
  }

  glEnable (GL_BLEND);
  glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    int i = 0, x = extents.left, ds = elem - 2;
    if (extents.left < 0) {
      i = -extents.left / elem;
      x = 0;
    }
    for (;i < n; ++i) {
      int l = x + 1, r = x + ds;
      glColor4f (clr.r, clr.g, clr.b, a0);
        glRecti (l, extents.bottom, r, extents.top);

			float dc = selection[i] * 0.4;
      glColor4f (clr.r + dc * clr.r, clr.g + dc * clr.g, clr.b + dc * clr.b, a);
        glRecti (l, extents.bottom, r, extents.bottom + heights[i]);
      x += elem;
      extern viewport view;
      if (x > view.xmax) break;
    }
  glDisable (GL_BLEND);

  sprintf (BUFFER, "%d/%.3f", lev+1, lev > -1? values[lev]:-1);
  draw_string (BUFFER, extents.left, extents.bottom - line_height, 0);

  if (editable) for (int i = 0, j = children.size(); i < j; ++i) children[i]->draw ();

}

int levels::change (int i, float dv) {
	if (i > -1 && i < n) {
		float& vi = values[i];
		vi += dv;
		int result = clamp<float> (0, vi, 1);
		heights[i] = (int) (vi * extents.height + 0.5); 
		return result;
	}
	return 0;
}

void levels::set_only (int i, float v) { 
	if (i > -1 && i < n) {
		clamp<float> (0, v, 1);
		values[i] = v;
		heights[i] = (int)(v * extents.height + 0.5);
	}
}

int levels::set (int i, float v, int h) { // from ui
  if (i > -1 && i < n) {
		int& hi = heights[i];
		int dh = h - hi;
		hi = h;
		float& vi = values[i];
		if (vi != v) { // bcos from ui
			vi = v;
		}
		return dh;
  }
	return 0;
}

void levels::update () {
	if (chgl) chgl->changed (*this);
}

void levels::chkpos () {
  extern viewport view;
  if (!view.inside (extents.left, extents.bottom)) extents.lower_corner (0, 0);
}

void levels::reheight () {
	for (int i = 0; i < n; ++i) {
		float v = values[i];
		int h = v * height + 0.5;
		heights[i] = h;
	}
}

int levels::lshift () { 

	float v0 = values[0];
	int h0 = heights[0];

  int wrap = cbwrap.state;

	if (wrap == 0) {
		if (h0) return 0;
	}

	for (int i = 0, j = 1; i < last; ++i, ++j) {
		values[i] = values[j];
		heights[i] = heights[j];
	}

  float& vl = values[last];
  int& hl = heights[last];
	if (wrap) {
		vl = v0;
		hl = h0;
	} else {
    vl = 0;
    hl = 0;
	}

  if (shftl) shftl->shifted (*this);

	return 1;

}

int levels::rshift () { 
	float vl = values[last];
	int hl = heights[last];

  int wrap = cbwrap.state;
	if (wrap == 0) {
		if (hl) return 0;
	}

	for (int j = last, i = last - 1; j > 0; --j, --i) {
		values[j]=values[i];
		heights[j]=heights[i];
	}

  float& v0 = values[0];
  int& h0 = heights[0];

	if (wrap) {
		v0 = vl;
		h0 = hl;
	} else {
		v0 = 0;
    h0 = 0;
	}

  if (shftl) shftl->shifted (*this);

	return 1;

}

void levels::selall () {
  nsel = 0;
	for (int i = 0; i < n; ++i) {
		if (heights[i]) {
      selection[i] = 1;
      ++nsel;
    }
	}
	clearbookmarks ();
}

void levels::selnon () {
	for (int i = 0; i < n; ++i) selection[i] = 0;
	nsel = 0;
	clearbookmarks ();
}

void levels::invsel () {
	for (int i = 0; i < n; ++i) {
		bool& si = selection[i];
		if (si) {
			si = 0; 
			--nsel;
		}
		else if (heights[i]) {
			si = 1;
			++nsel;
		}
	}
	clearbookmarks ();
}

CLICKED_BUTTON (levels, alllis) {
  levels* l = (levels*) data; 
  l->selall();
}

CLICKED_BUTTON (levels, nonlis) {
  levels* l = (levels*) data; 
  l->selnon();
}


CLICKED_BUTTON (levels, invlis) {
  levels* l = (levels*) data; 
  l->invsel();
}

CLICKED_BUTTON (levels, lshiftlis) {
  levels* l = (levels*) data; 
  l->lshift ();
}

CLICKED_BUTTON (levels, rshiftlis) {
  levels* l = (levels*) data; 
  l->rshift ();
}

CLICKED_BUTTON (levels, pluslis) {
  levels* l = (levels*) data; 
  l->addbookmark ();
}

void levels::slide::clicked (button& b) {
  if (!mouse_slider0.active) {
    mouse_slider0.add (this);
    activate_mouse_slider ();
  }
}

void levels::slide::moused (int dir, double scl) {
  levels* l = (levels*) data;
  if (dir > 0) l->rshift (); else l->lshift ();
}

void levels::set_pos (int x, int y) {
  widget::set_pos (x, y);
  extern int line_height;
  int bx = extents.right + 3, by = extents.top - line_height;
  blshift.set_pos (bx, by);
  brshift.set_pos (bx + 28, by);
  by -= line_height;
  {
    button* b[] = {&cbwrap, &bslide, &ball, &bnone, &binvert};
    for (int i = 0; i < 5; ++i) {
      button& bi = *b[i];
      bi.set_pos (bx, by);
      by -= line_height;
    }
  }
  {
    button* b[] = {&plus, &minus, &cross};
    for (int i = 0; i < 3; ++i) {
      b[i]->set_pos (bx, by);
      bx += 20;
    }
  }
  szr.set_pos (extents.right + elem, extents.bottom);
}

void levels::addbookmark () {

	if (nsel) {
		bookmark* bm = new bookmark ();
		int bs = bmk.size ();
		if (bs) {
			int lb = bs - 1;
			bookmark* plb = bmk [lb];
			bm->set_pos (minus.extents.left, plb->extents.bottom - state_button::SIZE2);
		} else {
			bm->set_pos (minus.extents.left, minus.extents.bottom - state_button::SIZE2);
		}

		for (int i = 0; i < n; ++i) if (selection[i]) bm->ids.push_back (i);

		clearbookmarks ();

		bm->set_state (1, 0);
		bm->set_listener (&bml);
			
		bmk.push_back (bm);
    this->add_child (bm);

    cons << GREEN << "Bookmarked " << nsel << spc << name << eol;

	} else {
		cons << RED << "No " << name << " selected to bookmark. SHIFT+click " << name << " to select." << eol;
	}

}

void levels::removebookmark () {
	for (vector<bookmark*>::iterator i = bmk.begin (), j = bmk.end (); i != j; ) {
		bookmark* b = *i;
		if (b->state) {
			i = bmk.erase (i);
			j = bmk.end ();
      this->remove_child (b);
      delete b;
		} else {
			++i;
		}
	}
}

void levels::removeallbookmarks () {
	for (int i = 0, j = bmk.size (); i < j; ++i) {
		bookmark* pbi = bmk[i];
		this->remove_child (pbi);
    delete pbi;
	}
	bmk.clear ();
}

void levels::clearbookmarks () {
	for (int i = 0, j = bmk.size (); i < j; ++i) bmk[i]->set_state (0,0);
}

CLICKED_CHECKBUTTON (levels, bmlis) {
	bookmark& b = dynamic_cast<bookmark&>(cb);
	int s = b.state;
  levels& l = *( (levels*) data);
	if (SHIFT)
		; 
	else {
		l.selnon ();
		if (s == 0) s = 1;
		b.set_state (s, 0);
	}

  int sl [] = {-1, 1};
	for (int i = 0, j = l.bmk.size (); i < j; ++i) {
		if (&b == l.bmk[i]) {
			vector<int>& ids = b.ids;
			int n = ids.size ();
			for (int m = 0; m < n; ++m) l.selection[ids[m]] = s;
			l.nsel += (n * sl[s]);
		}
	}
}

CLICKED_BUTTON (levels, minuslis) {
  ((levels*) data)->removebookmark ();
}

CLICKED_BUTTON (levels, crosslis) {
  ((levels*) data)->removeallbookmarks ();
}

/*MOVED (levels,szrlis) {
  levels* pl = (levels *) data;
  l->sized ();
}*/

void levels::moved () {
	if (extents.top > szr.extents.bottom) {
		height = extents.height = extents.top - szr.extents.bottom;
		set_pos (posx, szr.posy);
		reheight ();
	}
}
