#include "RichEdit.h"

namespace Upp {

void DiagramEditor::SetCursor(int i)
{
	cursor = i;
	if(i < 0) {
		sel.Clear();
		return;
	}
	sel.FindAdd(i);
	GetAttrs();
}

DiagramItem& DiagramEditor::CursorItem()
{
	static DiagramItem nil;
	if(cursor >= 0 && cursor < data.item.GetCount())
		return data.item[cursor];
	return nil;
}

DiagramItem& DiagramEditor::AddItem(int shape)
{
	int i = data.item.GetCount();
	if(shape == 0) { // insert lines before shapes
		i = 0;
		while(i < data.item.GetCount() && data.item[i].IsLine())
			i++;
		data.item.Insert(i);
	}
	DiagramItem& m = data.item.At(i);
	SetCursor(i);
	return m;
}

void DiagramEditor::SetAttrs(DiagramItem& m, dword attrs)
{
	if(attrs & ATTR_SHAPE)
		m.shape = ~shape;
	if(attrs & ATTR_CAP0)
		m.cap[0] = ~line_start;
	if(attrs & ATTR_CAP1)
		m.cap[1] = ~line_end;
	if(attrs & ATTR_WIDTH)
		m.width = ~line_width;
	if(attrs & ATTR_DASH)
		m.dash = ~line_dash;
	if(attrs & ATTR_INK)
		m.ink = ~ink;
	if(attrs & ATTR_PAPER)
		m.paper = ~paper;
}

void DiagramEditor::ForEachConst(Event<const DiagramItem&> fn) const
{
	for(int ii : sel)
		fn(data.item[ii]);
}

void DiagramEditor::ForEach(Event<DiagramItem&> fn)
{
	for(int ii : sel)
		fn(data.item[ii]);
	Sync();
	Commit();
}

void DiagramEditor::SetAttrs(dword attrs)
{
	if(tool >= 0)
		SetAttrs(tl[tool], attrs);
	ForEach([&](DiagramItem& m) {
		SetAttrs(m, attrs);
	});
}

void DiagramEditor::GetAttrs(const DiagramItem& m)
{
	shape <<= m.shape;
	line_start <<= m.cap[0];
	line_end <<= m.cap[1];
	line_width <<= m.width;
	line_dash <<= m.dash;
	ink <<= m.ink;
	paper <<= m.paper;
}


void DiagramEditor::GetAttrs()
{
	GetAttrs(CursorItem());
}

void DiagramEditor::MoveFrontBack(bool back)
{
	FinishText();
	Array<DiagramItem> item;
	Index<int> newsel;
	int        newcursor = -1;
	for(int pass = 0; pass < 2; pass++)
		for(int i = 0; i < data.item.GetCount(); i++) {
			bool isel = sel.Find(i) >= 0;
			bool b = pass;
			if(back)
				b = !b;
			if(isel == b) {
				int ii = item.GetCount();
				if(isel)
					newsel.Add(ii);
				if(cursor == i)
					newcursor = ii;
				item.Add(data.item[i]);
			}
		}
	data.item = pick(item);
	sel = pick(newsel);
	cursor = newcursor;
	Sync();
}

void DiagramEditor::Align(bool horz, int align)
{
	FinishText();

	if(!IsCursor())
		return;

	auto HoVe = [&](Pointf& p) -> double& {
		return horz ? p.x : p.y;
	};

	Pointf sz0 = (Point)data.GetSize();
	double dsz = HoVe(sz0);
	Rect cr = GetCursorRect();
	Pointf cp1 = cr.TopLeft();
	Pointf cp2 = cr.BottomRight();
	double csz = HoVe(cp2) - HoVe(cp1);
	PrepareConns();
	for(int ii : sel) {
		if(ii != cursor || align == ALIGN_NULL) {
			DiagramItem& m = data.item[ii];
			m.Normalize();
			Rectf r = m.GetRect();
			Pointf p1 = r.TopLeft();
			Pointf p2 = r.BottomRight();
			double sz = abs(HoVe(p2) - HoVe(p1));
			if(align == ALIGN_LEFT) {
				HoVe(p1) = HoVe(cp1);
				HoVe(p2) = HoVe(cp1) + sz;
			}
			if(align == ALIGN_RIGHT) {
				HoVe(p2) = HoVe(cp2);
				HoVe(p1) = HoVe(cp2) - sz;
			}
			if(align == ALIGN_CENTER) {
				HoVe(p1) = HoVe(cp1) + csz / 2 - sz / 2;
				HoVe(p2) = HoVe(p1) + sz;
			}
			if(align == ALIGN_JUSTIFY)
				HoVe(p2) = HoVe(p1) + csz;
			if(align == ALIGN_NULL) {
				HoVe(p1) = (dsz - csz) / 2;
				HoVe(p2) = HoVe(p1) + csz;
			}
			if(m.IsLine()) {
				m.pos = p1;
				m.size = p2 - p1;
			}
			else {
				m.pos = (p1 + p2) / 2;
				m.size = Pointf(abs(p1.x - p2.x), abs(p1.y - p2.y)) / 2;
			}
		}
	}
	UseConns();
	Commit();
}

void DiagramEditor::PrepareConns()
{
	conns.Clear();
	VectorMap<Pointf, Vector<Tuple<int, int>>> map;
	for(int pass = 0; pass < 2; pass++)
		for(int i = 0; i < data.item.GetCount(); i++) {
			const DiagramItem& m = data.item[i];
			if((int)m.IsLine() == pass) {
				Vector<Pointf> cp = m.GetConnections();
				for(int j = 0; j < cp.GetCount(); j++)
					map.GetAdd(cp[j]) << MakeTuple(i, j);
			}
		}
	for(int i = 0; i < data.item.GetCount(); i++) {
		const DiagramItem& m = data.item[i];
		if(m.IsLine()) {
			auto Add = [&](Pointf p, int j) {
				auto *q = map.FindPtr(p);
				if(q) {
					for(auto w : *q) {
						Cn& c = conns.Add();
						c.mi = w.a;
						c.ci = w.b;
						c.li = i;
						c.pi = j;
					}
				}
			};
			Add(m.pos, 0);
			Add(m.pos + m.size, 1);
		}
	}
}

void DiagramEditor::UseConns()
{
	for(const Cn& cn: conns)
		if(sel.Find(cn.li) < 0 && sel.Find(cn.mi) >= 0) {
			Pointf pt[2];
			DiagramItem& m = data.item[cn.li];
			pt[0] = m.pos;
			pt[1] = pt[0] + m.size;
			pt[cn.pi] = data.item[cn.mi].GetConnections()[cn.ci];
			m.pos = pt[0];
			m.size = pt[1] - m.pos;
		}
}

void DiagramEditor::ComputeAspectSize(DiagramItem& m, Sizef& sz_cx, Sizef& sz_cy)
{
	m.Normalize();
	Sizef sz = m.GetRect().GetSize();
	Sizef sz0 = m.GetStdSize(data);
	sz_cx = Sizef(max(sz.cx, 8.0), max(sz0.cy * sz.cx / sz0.cx, 8.0));
	sz_cy = Sizef(max(sz0.cx * sz.cy / sz0.cy, 8.0), max(sz.cy, 8.0));
}

struct SizeDlg : WithSizeLayout<TopWindow> {
	SizeDlg();

	static Size sz[];

	void Set(Size sz, Size asz);
	Size Get() const;
	void Sync();
};

Size SizeDlg::sz[] =
{
	Null,
	Size(1250, 1000),
	Size(1600, 900),
	Size(1600, 800),
	Size(1000, 1250),
};

Size SizeDlg::Get() const
{
	int q = ~size;
	if(q >= 0 && q < __countof(sz))
		return sz[q];
	auto cl = [](int x) { return clamp(x, 64, 10000); };
	return Size(cl(~cx), cl(~cy));
}

void SizeDlg::Sync()
{
	bool b = ~size == 5;
	cx.Enable(b);
	cy.Enable(b);
}

void SizeDlg::Set(Size sz, Size asz)
{
	cx <<= asz.cx;
	cy <<= asz.cy;
	int i = FindIndex(SubRange(SizeDlg::sz, __countof(SizeDlg::sz)), sz);
	size <<= i < 0 ? 5 : i;
	Sync();
}

SizeDlg::SizeDlg()
{
	CtrlLayoutOKCancel(*this, "Canvas size");
	size << [=] { Sync(); };
}

void DiagramEditor::ChangeSize()
{
	SizeDlg dlg;
	dlg.Set(data.size, data.GetSize());
	if(dlg.ExecuteOK()) {
		data.size = dlg.Get();
		Commit();
	}
}

}