#include "aps.hpp"
#include "box.h"

namespace box {

Ords::Ords(OrdId n) : vector<Ord>(n)
{
}

Ords::Ords(OrdId n, Ord v) : vector<Ord>(n, v)
{
}

Ords::Ords(const vector<Ord> &v) : vector<Ord>(v)
{
}

Ord Ords::getmin(void) const
{
	return (*std::min_element(begin(), end()));
}

Ord Ords::getmax(void) const
{
	return (*std::max_element(begin(), end()));
}

Ordss::Ordss(OrdId n) : vector<Ords>(n)
{
}

Ords Ordss::gettransposeords(OrdId i) const
{
	Ords ords;
	int j, n;

	ords = Ords(n = size());
	for (j = 0; j < n; j++) ords[j] = (*this)[j][i];
	return (ords);
}

Ords Ordss::getmin(void) const
{
	Ords ords;
	OrdId i, m;

	m = (*this)[0].size();
	ords = Ords(m);
	for (i = 0; i < m; i++)
		ords[i] = gettransposeords(i).getmin();
	return (ords);
}

Ords Ordss::getmax(void) const
{
	Ords ords;
	OrdId i, m;

	m = (*this)[0].size();
	ords = Ords(m);
	for (i = 0; i < m; i++)
		ords[i] = gettransposeords(i).getmax();
	return (ords);
}

Space::Space(OrdId n, Ord len)
{
	init(Ords(n, len));
}

Space::Space(const Ords &alens)
{
	init(alens);
}

void Space::init(const Ords &alens)
{
	Ords::const_iterator il;

	lens = alens;
	nloc = 1;
	for (il = lens.begin(); il != lens.end(); nloc *= *il++);
}

Ords Space::getlens(void) const
{
	return (lens);
}

LocId Space::getnloc(void) const
{
	return (nloc);
}

LocId Space::getloc(const Ords &ords) const
{
	LocId loc;
	Ords::const_reverse_iterator il, io;

	loc = 0;
	io = ords.rbegin();
	for (il = lens.rbegin(); il != lens.rend(); il++, io++)
		loc *= *il, loc += *io;
	return (loc);
}

Ords Space::getords(LocId loc) const
{
	Ords ords;
	Ords::const_iterator il;
	Ords::iterator io;

	ords = Ords(lens.size());
	io = ords.begin();
	for (il = lens.begin(); il != lens.end(); il++, io++)
		*io = loc % *il, loc /= *il;
	return (ords);
}

Map Space::map(void) const
{
	return (Map(nloc));
}

Map Space::map(OrdId i) const
{
	Map map;
	Ords ords;
	LocId k;

	map = Map(nloc);
	for (k = 0; k < nloc; k++) {
		ords = getords(k);
		ords[i] = lens[i] - 1 - ords[i];
		map[k] = getloc(ords);
	}
	return (map);
}

Map Space::map(OrdId i, OrdId j) const
{
	Map map;
	Ords ords;
	LocId k;

	map = Map(nloc);
	for (k = 0; k < nloc; k++) {
		ords = getords(k);
		std::swap(ords[i], ords[j]);
		map[k]= getloc(ords);
	}
	return (map);
}

Maps Space::maps(void) const
{
	Maps maps;
	Db<Ord,int> db;
	Ords::const_iterator il;
	vector<OrdId> v;
	OrdId i, j;
	int k;

	for (il = lens.begin(), i = 0; il != lens.end(); il++, i++) {
		k = db.set(*il);
		if (k == v.size()) v.push_back(i);
		j = v[k];
		maps.push_back(i == j? map(i): map(i, j));
	}
	return (maps);
}

Maps Space::maps(bool reflectflag) const
{
	Maps::iterator im;
	Maps src, dst;
	Map m;

	src = maps();
	im = src.begin();
	if (reflectflag || im == src.end()) return (src);
	for (m = *im++; im != src.end(); im++)
		dst.push_back(m * *im);
	return (dst);
}

Ordss Space::getordss(const Pos &pos) const
{
	Ordss ordss;
	int i, n;

	n = pos.size();
	ordss = Ordss(n);
	for (i = 0; i < n; i++)
		ordss[i] = getords(pos[i]);
	return (ordss);
}

Pos Space::getpos(const Ordss &ordss) const
{
	Pos pos;
	int i, n;

	n = ordss.size();
	pos = Pos(n);
	for (i = 0; i < n; i++)
		pos[i] = getloc(ordss[i]);
	return (pos);
}

Poss Space::getrotates(const Pos &pos) const
{
	Poss poss;
	Pos p;
	Ords maxords;
	Ordss ordss, o;
	vector<OrdId> permute, lastpermute;
	vector<OrdId>::iterator pi;
	OrdId i, m;
	int j, n, reflect;
	Maps reflectmaps;

	reflectmaps = map(0).nestlist(2);
	ordss = getordss(map().rot(nloc - getloc(getordss(pos).getmin())) * pos);
	maxords = ordss.getmax();
	m = lens.size();
	n = pos.size();
	o = Ordss(m);
	p = Pos(n);
	permute = vector<int>(m);
	for (i = 0; i < m; i++) permute[i] = i;
	reflect = permute.begin() - permute.end() >> 1 & 1;
	for (pi = permute.begin(); pi != permute.end();
			pi = ::next_permutation(permute.begin(), permute.end())) {
		reflect ^= pi - permute.end() >> 1 & 1;
		for (i = 0; i < m; i++) if (maxords[permute[i]] >= lens[i]) break;
		if (i < m) continue;
		for (i = 0; i < m; i++) o[i] = ordss.gettransposeords(permute[i]);
		for (j = 0; j < n; j++) p[j] = getloc(o.gettransposeords(j));
		poss.push_back(reflectmaps[reflect] * p);
	}
	return (poss);
}

Poss Space::getrotates(const Poss &poss) const
{
	Poss p;
	Poss::const_iterator ip;

	for (ip = poss.begin(); ip != poss.end(); ip++)
		p.append(getrotates(*ip));
	return (p);
}

Poss Space::gettranslates(const Pos &pos) const
{
	Poss poss;
	Pos p;
	Ords maxords;
	LocId s;
	OrdId i, n;

	p = map().rot(nloc - getloc(getordss(pos).getmin())) * pos;
	maxords = getordss(p).getmax();
	poss = Poss(p);
	n = lens.size();
	for (i = 0, s = 1; i < n; s *= lens[i++])
		poss = map().rot(s).nestlist(lens[i] - maxords[i] - 1) * poss;
	return (poss);
}

Poss Space::gettranslates(const Poss &poss) const
{
	Poss p;
	Poss::const_iterator ip;

	for (ip = poss.begin(); ip != poss.end(); ip++)
		p.append(gettranslates(*ip));
	return (p);
}

Poss Space::gettransforms(const Poss &poss, bool reflectflag) const
{
	return (maps(reflectflag).close() * gettranslates(getrotates(poss)));
}

void Space::printheading(std::ostream &output, const Ords &ords) const
{
	Ords::const_reverse_iterator io;

	for (io = ords.rbegin(); io != ords.rend() - 3; io++)
		output << '[' << *io << ']';
	output << ':' << std::endl;
}

void Space::print(std::ostream &output, const std::string &codes) const
{
	LocId i[4], inc[4], s;
	OrdId j;

	for (j = 0, s = 1; j < 4; j++)
		inc[j] = s, s *= j < lens.size()? lens[j]: 1;
	for (i[3] = 0; i[3] < nloc; i[3] += inc[3]) {
		if (lens.size() > 3) printheading(output, getords(i[3]));
		for (i[1] = i[3]; i[1] < i[3] + inc[2]; i[1] += inc[1]) {
			for (i[2] = i[1]; i[2] < i[1] + inc[3]; i[2] += inc[2]) {
				for (i[0] = i[2]; i[0] < i[2] + inc[1]; i[0] += inc[0])
					output << codes[i[0]] << " ";
				output << "   ";
			}
			output << std::endl;
		}
		output << std::endl;
	}
}

Box::Box(OrdId anord, Ord alen)
	: Space(anord, alen),
	  puzzle(getnloc()),
	  pos(getnloc()),
	  reflectinflag(false), reflectoutflag(false)
{
}

Box::Box(const Ords &alens)
	: Space(alens),
	  puzzle(getnloc()),
	  pos(getnloc()),
	  reflectinflag(false), reflectoutflag(false)
{
}

void Box::setpos(const Pos &apos)
{
	pos = apos;
}

void Box::setreflect(bool areflectflag)
{
	setreflectin(areflectflag);
	setreflectout(areflectflag);;
}

void Box::setreflectin(bool areflectinflag)
{
	reflectinflag = areflectinflag;
}

void Box::setreflectout(bool areflectoutflag)
{
	reflectoutflag = areflectoutflag;
}

void Box::setshapes(const Shapes &ainputshapes)
{
	inputshapes = ainputshapes;
}

Shapes Box::transformshapes(bool reflectflag)
{
	Shapes::const_iterator is;
	Shapes shapes;

	for (is = inputshapes.begin(); is != inputshapes.end(); is++)
		shapes.push_back(Shape(is->n, gettransforms(is->poss, reflectflag)));
	return (shapes);
}

void Box::search(search::ServerBase &searchserver)
{
	puzzle.setpos(pos);
	puzzle.setinputmaps(maps(reflectinflag).close().symmetric(Poss(pos)));
	puzzle.setoutputmaps(maps(reflectoutflag).close().symmetric(Poss(pos)));
	puzzle.setshapes(transformshapes(reflectinflag));
	puzzle.search(searchserver);
}

void Box::printsols(std::ostream &output, const std::string &codes)
{
	PartIdss::const_iterator ipid;
	PartIdss partidss;
	Parts parts;
	int i;
	std::string tab, src;

	partidss = getsolutions();
	parts = getparts();
	src = std::string(getnloc(), '.');
	i = 0;
	for (ipid = partidss.begin(); ipid != partidss.end(); ipid++) {
		tab = sort(parts.select(*ipid)).decode(codes, src);
		output << "Solution " << ++i << ":" << std::endl;
		Space::print(output, tab);
	}
}

void Box::printshapes(std::ostream &output, const std::string &codes)
{
	Parts parts;
	ShapeId shapeid, nshapeid;
	int n;
	Parts::const_iterator pi;
	std::string tab, src;
	std::string::const_iterator si;

	src = std::string(getnloc(), '.');
	nshapeid = inputshapes.size();
	si = codes.begin();
	for (shapeid = 0; shapeid < nshapeid; shapeid++, si++) {
		n = 0;
		parts = sort(puzzle.getmappedparts().select(shapeid));
		for (pi = parts.begin(); pi != parts.end(); pi++) {
			tab = Parts(*pi).decode(std::string(1, *si), src);
			output << "Shape " << codes[shapeid]
					<< ", Position " << ++n << ":" << std::endl;
			Space::print(output, tab);
		}
	}
}

void Box::print(std::ostream &output,
		const std::string &partcodes,
		const std::string &shapecodes)
{
	printsols(output, partcodes);
	printshapes(output, shapecodes);
}

PartIdss &Box::getsolutions(void)
{
	return (puzzle.getsolutions());
}

Parts &Box::getparts(void)
{
	return (puzzle.getparts());
}

} // end of namespace box
