/***************************************************************************
                           questiongenerator.cpp
                           -------------------
    copyright            : (C) 2004 by Jos van den Oever
    email                : jos@vandenoever.info
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
#include "questiongenerator.h"
#include "corner.h"
#include <iostream>
using std::cerr;
using std::endl;
using std::flush;

QuestionGenerator::QuestionGenerator() {
	nb = 3;
	nC1 = nC2 = nC4 = -1;
	ndC1 = ndC2 = ndC4 = -1;
	smC1 = smC2 = smC4 = 0;
	uniqueSides = false;
}
void
QuestionGenerator::setStyleRestrictions(ushort mC1, ushort mC2, ushort mC4) {
	smC1 = mC1;
	smC2 = mC2;
	smC4 = mC4;
	setUniqueSides(uniqueSides);
}
void
QuestionGenerator::setUniqueSides(bool b) {
	uniqueSides = b;
}
void
QuestionGenerator::setBlanks(short blanks) {
	nb = blanks;
	if (nb > 4) nb = 4;
}
void
QuestionGenerator::setNumSyms(short mC1, short mC2, short mC4) {
	short t = 0;
	if (mC1 < 0) {
		mC1 = -1;
	} else {
		t += mC1;
	}
	if (mC2 < 0) {
		mC2 = -1;
	} else {
		t += mC2;
	}
	if (mC4 < 0) {
		mC4 = -1;
	} else {
		t += mC4;
	}
	if (t > 6) {
		nC1 = nC2 = nC4 = -1;
	} else {
		nC1 = mC1;
		nC2 = mC2;
		nC4 = mC4;
	}
}
void
QuestionGenerator::setDiffSyms(short mC1, short mC2, short mC4) {
	ndC1 = mC1;
	ndC2 = mC2;
	ndC4 = mC4;
}
bool
QuestionGenerator::makeQuestion(Question &q) const {
	Question orig = q;
	short count = 0;
	do {
		q = orig;
		shuffleCube(q);
		makeAnswers(q);
	} while (!q.valid && ++count < 10000);
	if (q.valid) {
		shuffleSides(q);
	}
	return q.valid;
}
Question
QuestionGenerator::makeQuestion() const {
	Question q;
	short count = 0;
	if (smC4 == 0 || smC1 + smC2 + smC4 < 2) {
		q.valid = false;
		return q;
	}
	do {
		q = makeOriginal();
		q.valid = true;
		makeAnswers(q);
	} while (!q.valid && ++count < 100);
	if (q.valid) {
		shuffleSides(q);
	} else {
//		cerr << "failed to make a question" << endl;
	}
	return q;
}
void
QuestionGenerator::makeAnswers(Question &q) const {
	for (short i=0; i<6; ++i) {
		q.shownTypes[i] = q.cube.type[i];
		q.shownSyms[i] = q.cube.sym[i];
	}
	Corner vc[4];
	Corners cor(q.cube);
	q.valid = !cor[0].is2_3Blank();
	q.answer = rand(4);
	for (short i=0; i<4 && q.valid; ++i) {
		q.answers[i] = q.cube;
		short o;
		short count = 0;
		if (i == q.answer) {
		//	do {
				o = rand(20)+1; // never show original orient
				if (o >= 18) o += 3; // never show backside
		//	} while (cor[o/3].is2_3Blank() && ++count < 100);
//			if (count == 1000) q.valid = false;
		} else {
			bool dupe;
			do {
				dupe = false;
				if (count%5 == 0) {
					q.answers[i] = q.cube;
				}
				mutate(q.answers[i]);
				o = cor.chooseOrientation( q.answers[i],
					q, vc[i]);
				count++;
				// check if we already have this corner
				for (short j=0; !dupe && j<i; ++j) {
					if (o != -1 && q.answer != j) {
						dupe = vc[i] == vc[j];
					}
				}
				if (o != -1) {
					dupe |= vc[i].isBlank();
				}
			} while ((dupe || o == -1) && ++count < 30);
			if (dupe || o == -1) q.valid = false;
		}
		q.answers[i].orientation = o;
	}
}
void
QuestionGenerator::mutate(CubeDecoration &c) const {
	short s1, s2;
	s1 = rand(3);
	if (c.sym[s1] == C4 || rand(2) > 0) {
		do {
			s2 = rand(6);
		} while (s1 == s2);
		// swap
		short t = c.type[s1];
		c.type[s1] = c.type[s2];
		c.type[s2] = t;
		Symmetry s = c.sym[s1];
		c.sym[s1] = c.sym[s2];
		c.sym[s2] = s;
		Rotation r = c.siderot[s1];
		c.siderot[s1] = c.siderot[s2];
		c.siderot[s2] = r;
	} else {
		if (c.sym[s1] == C2) {
			c.siderot[s1] = Rotation((c.siderot[s1]+1)%4);
		} else {
			short r = rand(3);
			c.siderot[s1] = Rotation((c.siderot[s1]+r)%4);
		}
	}
}
void
QuestionGenerator::getTypeAmounts(short &nnC1, short &nnC2, short &nnC4) const{
	// copy the set values
	short tnC1, tnC2, tnC4;
	tnC1 = nC1; tnC2 = nC2; tnC4 = nC4;
	if (smC1 == 0) {
		tnC1 = 0;
		if (smC2 == 0) {
			nnC1 = nnC2 = 0;
			nnC4 = 6-nb;
			return;
		}
		if (smC4 == 1) {
			nnC1 = nnC4 = 0;
			nnC2 = 6-nb;
			return;
		}
	}
	if (smC2 == 0) {
		tnC2 = 0;
		if (smC4 == 1) {
			nnC1 = 6-nb;
			nnC2 = nnC4 = 0;
			return;
		}
	}
	if (smC4 == 1) tnC4 = 0;
	// if uniqueSides is set, take the side restrictions into account
	if (uniqueSides && smC1 + smC2+ smC4 >= 6) {
		if (tnC1 > smC1) tnC1 = smC1;
		if (tnC2 > smC2) tnC2 = smC2;
		if (tnC4 > smC4-1) tnC4 = smC4-1;
	}
	// if only one value is not set, set it
	if (tnC1 != -1 && tnC2 != -1) {
		if (tnC4 == -1) {
			tnC4 = 6 - nb - tnC1 - tnC2;
			if (tnC4 < 0) {
				tnC4 = tnC1 = tnC2 = -1;
			}
		} else if (tnC1 + tnC2 + tnC4 + nb != 6) {
			tnC1 = tnC2 = tnC4 = -1;
		}
	} else if (tnC1 != -1 && tnC4 != -1) {
		tnC2 = 6 - nb - tnC1 - tnC4;
		if (tnC2 < 0) {
			tnC4 = tnC1 = tnC2 = -1;
		}
	} else if (nC2 != -1 && nC4 != -1) {
		tnC1 = 6 - nb - tnC2 - tnC4;
		if (tnC1 < 0) {
			tnC4 = tnC1 = tnC2 = -1;
		}
	}
	if (tnC1 != -1 && tnC2 != -1) {
		nnC1 = tnC1; nnC2 = tnC2; nnC4= tnC4;
		return;
	}

	short left = 6 - nb;
	nnC1 = nnC2 = nnC4 = 0;
	if (tnC1 != -1) {
		if (left < tnC1) {
			tnC1 = left;
		}
		nnC1 = tnC1;
		left -= tnC1;
	} else if (tnC2 != -1) {
		if (left < tnC2) {
			tnC2 = left;
		}
		nnC2 = tnC2;
		left -= tnC2;
	} else if (tnC4 != -1) {
		if (left < tnC4) {
			tnC4 = left;
		}
		nnC4 = tnC4;
		left -= tnC4;
	}
	short i=0;
	do {
		short s = rand(3);
		switch (s) {
		case 0:
			if (tnC1 == -1 && smC1 > 0
					&& (!uniqueSides || nnC1 < smC1)) {
				nnC1++;
				i++;
			}
			break;
		case 1:
			if (tnC2 == -1 && smC2 > 0
					&& (!uniqueSides || nnC2 < smC2)) {
				nnC2++;
				i++;
			}
			break;
		case 2:
			if (tnC4 == -1 && smC4 > 1
					&& (!uniqueSides || nnC4 < smC4-1)) {
				nnC4++;
				i++;
			}
			break;
		}
	} while (i < left);
}
void
QuestionGenerator::makemap(short *map, short n, short min, short max) {
	if (n > max-min) {
		cerr << "makemap: cannot take "<<n<<" numbers out of "
			<< (max-min) << " possibilities." << endl;
		return;
	}
	short i;
	for (i=0; i<n; ++i) {
		short m;
		bool taken;
		do {
			m = rand(max-min)+min;
			taken = false;
			for (short j=0; !taken && j<i; ++j) {
				taken = map[j] == m;
			}
		} while(taken);
		map[i] = m;
	}
}
Question
QuestionGenerator::makeOriginal() const {
	short tnC1, tnC2, tnC4;
	short tndC1, tndC2, tndC4;
	getTypeAmounts(tnC1, tnC2, tnC4);
	if (uniqueSides && smC1 + smC2+ smC4 >= 6) {
		tndC1 = tnC1;
		tndC2 = tnC2;
		tndC4 = tnC4;
	} else {
		tndC1 = ndC1;
		tndC2 = ndC2;
		tndC4 = ndC4;
	}
	if (tndC1 == -1) tndC1 = rand(tnC1+1);
	if (tndC2 == -1) tndC2 = rand(tnC2+1);
	if (tndC4 == -1) tndC4 = rand(tnC4+1);
	if (tndC1 > smC1) tndC1 = smC1;
	if (tndC2 > smC2) tndC2 = smC2;
	if (tndC4 > smC4-1) tndC4 = smC4-1;

	// first fill the type and sym array with the used sides
	// then shuffle them
	short i;
	Question q;
	for (i=0; i<tnC1; ++i) {
		q.shownSyms[i] = C1;
		if (i < tndC1) {
			q.shownTypes[i] = i;
		} else if (tndC1 == -1) {
			q.shownTypes[i] = rand(smC1);
		} else {
			q.shownTypes[i] = rand(tndC1);
		}
	}
	for (i=0; i<tnC2; ++i) {
		q.shownSyms[i+tnC1] = C2;
		if (i < tndC2) {
			q.shownTypes[i+tnC1] = i;
		} else if (tndC2 == -1) {
			q.shownTypes[i+tnC1] = rand(smC2);
		} else {
			q.shownTypes[i+tnC1] = rand(tndC2);
		}
	}
	for (i=1; i<tnC4+1; ++i) {
		q.shownSyms[i+tnC1+tnC2-1] = C4;
		if (i < tndC4+1) {
			q.shownTypes[i+tnC1+tnC2-1] = i;
		} else if (tndC4 == -1) {
			q.shownTypes[i+tnC1+tnC2-1] = 1 + rand(smC4-1);
		} else {
			q.shownTypes[i+tnC1+tnC2-1] = 1 + rand(tndC4);
		}
	}
	for (i=0; i<nb; ++i) {
		q.shownSyms[i+tnC1+tnC2+tnC4] = C4;
		q.shownTypes[i+tnC1+tnC2+tnC4] = 0;
	}
	shuffleCube(q);
	for (i=0; i<6; ++i) {
		if (q.cube.type[i] > 5) {
			cerr << "type out of range: " << q.cube.type[i] << endl;
		}
	}
	return q;
}
void
QuestionGenerator::shuffleSides(Question &q) const {
	short m1, m2, m4;
	m1 = m2 = m4 = 0;
	for (short i=0; i<6; ++i) {
		Symmetry s = q.cube.sym[i];
		short t = q.cube.type[i];
		if (s == C1 && t >= m1) {
			m1 = t+1;
		} else if (s == C2 && t >= m2) {
			m2 = t+1;
		} else if (s == C4 && t >= m4) {
			m4 = t+1;
		}
	}

	for (short i=0; i<6; ++i) {
		if (q.cube.type[i] > 5) {
			cerr << "type out of range before" << endl;
		}
	}
/*	for (short i=0; i<6; ++i) {
		cerr << q.cube.type[i] << " " << q.cube.sym[i] << "  ";
	}
	cerr << endl;
	cerr << m1 << " " << m2 << " " << m4 << endl;*/
	short mapC1[6], mapC2[6], mapC4[6];
	makemap(mapC1, m1, 0, smC1);
	makemap(mapC2, m2, 0, smC2);
	mapC4[0] = 0;
	makemap(mapC4+1, m4-1, 1, smC4);
	for (short i=0; i<6; ++i) {
		short *t = q.cube.type + i;
		if (q.cube.sym[i] == C1) {
			*t = mapC1[*t];
		} else if (q.cube.sym[i] == C2) {
			*t = mapC2[*t];
		} else {
			*t = mapC4[*t];
		}
		for (short j=0; j<4; ++j) {
			t = q.answers[j].type + i;
			if (q.answers[j].sym[i] == C1) {
				*t = mapC1[*t];
			} else if (q.answers[j].sym[i] == C2) {
				*t = mapC2[*t];
			} else {
				*t = mapC4[*t];
			}
		}
	}
/*	for (short i=0; i<6; ++i) {
		cerr << q.cube.type[i] << " " << q.cube.sym[i] << "  ";
	}
	cerr << endl;*/
	for (short i=0; i<6; ++i) {
		if (q.cube.type[i] > 20 || q.cube.type[i] < 0) {
			cerr << "type out of range after" << endl;
		}
	}
}
void
QuestionGenerator::shuffleCube(Question &q) const {
	CubeDecoration c;
	// shuffle the order
	short map[6];
	makemap(map, 6, 0, 6);
	for (short i=0; i<6; ++i) {
		c.type[i] = q.shownTypes[map[i]];
		c.sym[i] = q.shownSyms[map[i]];
		c.siderot[i] = Rotation(rand(4));
	}

	// optimization idea: move blank sides to the end
	// or else the cube is discarded
	bool b0 = c.type[0] == 0 && c.sym[0] == C4;
	bool b1 = c.type[1] == 0 && c.sym[1] == C4;
	bool b2 = c.type[2] == 0 && c.sym[2] == C4;
	short tt;
	Symmetry ts;
	if (b2) {
		if (b1) {
			//shift 2 to the right
			tt = c.type[0];        ts = c.sym[0];
			c.type[0] = c.type[4]; c.sym[0] = c.sym[4];
			c.type[4] = c.type[2]; c.sym[4] = c.sym[2];
			c.type[2] = tt;        c.sym[2] = ts;
			tt = c.type[1];        ts = c.sym[1];
			c.type[1] = c.type[5]; c.sym[1] = c.sym[5];
			c.type[5] = c.type[3]; c.sym[5] = c.sym[3];
			c.type[3] = tt;        c.sym[3] = ts;
		} else if (b0) {
			// shift 3 to the right
			for (short i=0; i<3; ++i) {
				tt          =c.type[0+i];ts         =c.sym[0+i];
				c.type[0+i] =c.type[3+i];c.sym[0+i] =c.sym[3+i];
				c.type[3+i] =tt;         c.sym[3+i] =ts;
			}
		}
	} else if (b0 && b1) {
		// shift 2 to the left
		tt = c.type[0];        ts = c.sym[0];
		c.type[0] = c.type[2]; c.sym[0] = c.sym[2];
		c.type[2] = c.type[4]; c.sym[2] = c.sym[4];
		c.type[4] = tt;        c.sym[4] = ts;
		tt = c.type[1];        ts = c.sym[1];
		c.type[1] = c.type[3]; c.sym[1] = c.sym[3];
		c.type[3] = c.type[5]; c.sym[3] = c.sym[5];
		c.type[5] = tt;        c.sym[5] = ts;
	}
	q.cube = c;
}
