// Brain Party
// Copyright (C) 2010 Paul Hudson (http://www.tuxradar.com/brainparty)

// Brain Party 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 3
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#include "wordsmash.h"
#include "Minigame.h"

BPMiniGame_WordSmash::BPMiniGame_WordSmash(BPGame* game) : BPMiniGame(game) {
	TheGame = game;
	
	sfcBackgroundTop = TheGame->LoadBitmap("wordsmash_top", 320, 480);
	sfcBackgroundBottom = TheGame->LoadBitmap("wordsmash_bottom", 320, 480);
	
	SelectedRow = NULL;
	Locked = false; // when true disallow movement
	TimeStarted = LastColour = 0;
	SuccessTime = -1;
	
	GotMovement = JustMoved = false;
	
	DisappearTime = 250;
	
	sfcLetters["a"] = TheGame->LoadBitmap("a", 44, 44);
	sfcLetters["b"] = TheGame->LoadBitmap("b", 44, 44);
	sfcLetters["c"] = TheGame->LoadBitmap("c", 44, 44);
	sfcLetters["d"] = TheGame->LoadBitmap("d", 44, 44);
	sfcLetters["e"] = TheGame->LoadBitmap("e", 44, 44);
	sfcLetters["f"] = TheGame->LoadBitmap("f", 44, 44);
	sfcLetters["g"] = TheGame->LoadBitmap("g", 44, 44);
	sfcLetters["h"] = TheGame->LoadBitmap("h", 44, 44);
	sfcLetters["i"] = TheGame->LoadBitmap("i", 44, 44);
	sfcLetters["j"] = TheGame->LoadBitmap("j", 44, 44);
	sfcLetters["k"] = TheGame->LoadBitmap("k", 44, 44);
	sfcLetters["l"] = TheGame->LoadBitmap("l", 44, 44);
	sfcLetters["m"] = TheGame->LoadBitmap("m", 44, 44);
	sfcLetters["n"] = TheGame->LoadBitmap("n", 44, 44);
	sfcLetters["o"] = TheGame->LoadBitmap("o", 44, 44);
	sfcLetters["p"] = TheGame->LoadBitmap("p", 44, 44);
	sfcLetters["qu"] = TheGame->LoadBitmap("qu", 44, 44);
	sfcLetters["r"] = TheGame->LoadBitmap("r", 44, 44);
	sfcLetters["s"] = TheGame->LoadBitmap("s", 44, 44);
	sfcLetters["t"] = TheGame->LoadBitmap("t", 44, 44);
	sfcLetters["u"] = TheGame->LoadBitmap("u", 44, 44);
	sfcLetters["v"] = TheGame->LoadBitmap("v", 44, 44);
	sfcLetters["w"] = TheGame->LoadBitmap("w", 44, 44);
	sfcLetters["x"] = TheGame->LoadBitmap("x", 44, 44);
	sfcLetters["y"] = TheGame->LoadBitmap("y", 44, 44);
	sfcLetters["z"] = TheGame->LoadBitmap("z", 44, 44);
	
	Words = new WordList("Content/wordlist.txt");
	
	for (int j = 0; j < RowCount; ++j) {
		vector<BPMiniGame_WordSmash_Letter*>* row = new vector<BPMiniGame_WordSmash_Letter*>();
		
		for (int i = 0; i < ColCount; ++i) {
			BPMiniGame_WordSmash_Letter* box = new BPMiniGame_WordSmash_Letter();
			box->X = LeftIndent + (i * BoxSize);
			box->Y = -BoxSize;
			box->DestY = 57 + (j * BoxSize);
			
			box->Letter = GetLetter();
			row->push_back(box);
		}
		
		Boxes.push_back(row);
	}
	
	GameTitle = "Word Smash";
	GameHelp = "Drag the rows around, then tap squares to make words of four letters or more. Double-tap the last letter when you want to submit it; if you change your mind, just tap the first letter again to clear it.";
	GameHelp2 = "You can spell words in any direction, even changing direction part way if you want to. For example, you could go down a letter, right a letter, down a letter, then right one more then tap the last letter again to submit it. You get more points for long words, so be creative!";
	
	MiniGameType = PUZZLE;
	
	Score = 0;
	sfcScoreStr = sfcClock = NULL;
	SetScore();
}

BPMiniGame_WordSmash::~BPMiniGame_WordSmash() {
	SAFE_DELETE(sfcBackgroundTop);
	SAFE_DELETE(sfcBackgroundBottom);
	
	BPMiniGame_WordSmash_Letter* box;
	
	for (int i = 0; i < Boxes.size(); ++i) {
		vector<BPMiniGame_WordSmash_Letter*>* row = Boxes[i];
		
		for (int i = row->size() - 1; i >= 0; --i) {
			box = (*row)[i];
			SAFE_DELETE(box);
		}
		
		row->clear();
		SAFE_DELETE(row);
	}
		
	Boxes.clear();
	
	for(map<string, Texture*>::iterator it = sfcLetters.begin(); it != sfcLetters.end(); ++it) {
		SAFE_DELETE(it->second);
	}
		
	sfcLetters.clear();
	
	SAFE_DELETE(Words);
	
	SAFE_DELETE(sfcScoreStr);
	SAFE_DELETE(sfcClock);
}

int BPMiniGame_WordSmash::GetWeight() {
	return Score / 100;
}

void BPMiniGame_WordSmash::OnMouseMove() {
	if (Locked) return;
	if (SelectedRow == NULL) return;
	
	if (!GotMovement) {
		Moves.Clear();
	}
	
	GotMovement = true;
	
	BPMiniGame_WordSmash_Letter* box;
	
	if (LastPos != BPPoint::Zero && LastPos != TouchEvent) {
		if (TouchEvent.X < LastPos.X) {
			// move left
			
			for (int i = SelectedRow->size() - 1; i >= 0; --i) {
				box = (*SelectedRow)[i];
				
				box->X += TouchEvent.X - LastPos.X;
				
				if (box->X <= -BoxSize) {
					box->X = GameWidth + box->X - LeftIndent - RightIndent;
					SelectedRow->erase(SelectedRow->begin() + i);
					SelectedRow->push_back(box);
				}
			}
			
		} else {
			// move right
			
			for (int i = 0; i < SelectedRow->size(); ++i) {
				box = (*SelectedRow)[i];
				
				box->X += TouchEvent.X - LastPos.X;
				
				if (box->X >= GameWidth - RightIndent) {
					box->X -= GameWidth - LeftIndent - RightIndent;
					SelectedRow->erase(SelectedRow->begin() + i);
					SelectedRow->insert(SelectedRow->begin() + 0, box);
				}
			}
		}
	}
	
	LastPos = TouchEvent;
}

void BPMiniGame_WordSmash::OnMouseDown() {
	if (Locked) return;
	GotMovement = false;
	
	LastPos = TouchEvent;
	
	for (int i = 0; i < Boxes.size(); ++i) {
		if (TheGame->PointOverRect(TouchEvent.X, TouchEvent.Y, 0, 37 + (i * BoxSize) + HalfBoxSize, 320, BoxSize)) {
			// move this row
			SelectedRow = Boxes[i];
			break;
		}
	}
}

void BPMiniGame_WordSmash::OnMouseUp() {
	if (Locked) return;
	if (SelectedRow == NULL) return;
	
	if (GotMovement) {
		
		BPMiniGame_WordSmash_Letter* box;
		
		// we've let go of a row - snap it into place!
		int xoffset = (*SelectedRow)[0]->X - LeftIndent;
		
		int remainder = xoffset % BoxSize;
		if (remainder != 0) {
			int adjust = 0;
			
			if (remainder > HalfBoxSize || (remainder < 0 && remainder > -HalfBoxSize)) {
				// snap to the right
				if (remainder > 0) {
					adjust = BoxSize - remainder;
				} else {
					adjust = abs(remainder);
				}
				
				for (int i = 0; i < SelectedRow->size(); ++i) {
					box = (*SelectedRow)[i];
					
					box->X += adjust;
					
					if (box->X >= GameWidth - RightIndent) {
						box->X -= GameWidth;
						SelectedRow->erase(SelectedRow->begin() + i);
						SelectedRow->insert(SelectedRow->begin() + 0, box);
					}
				}
			} else {
				// snap to the left
				if (remainder < 0) {
					adjust = abs(remainder) - BoxSize;
				} else {
					adjust = -remainder;
				}
				
				for (int i = SelectedRow->size() - 1; i >= 0; --i) {
					box = (*SelectedRow)[i];
					
					box->X += adjust;
					
					if (box->X <= LeftIndent - BoxSize) {
						box->X = GameWidth + box->X;
						SelectedRow->erase(SelectedRow->begin() + i);
						SelectedRow->push_back(box);
					}
				}
				
			}
		}
		
		SelectedRow = NULL;
	} else {
		// find the box that was clicked on
		BPList<BPMiniGame_WordSmash_Letter*> Possibles;
		BPMiniGame_WordSmash_Letter* box;
		
		for (int i = 0; i < Boxes.size(); ++i) {
			vector<BPMiniGame_WordSmash_Letter*>* row = Boxes[i];
			
			for (int j = row->size() - 1; j >= 0; --j) {
				box = (*row)[j];
				
				if (TheGame->PointOverRect(TouchEvent.X, TouchEvent.Y, box->X - HalfBoxSize, box->Y - HalfBoxSize, BoxSize * 2, BoxSize * 2)) {
					Possibles.Add(box);
				}
			}
		}
		
		box = NULL;
		int maxdist = 999;
		
		for (int i = 0; i < Possibles.Count; ++i) {
			int centrex = Possibles[i]->X + HalfBoxSize;
			int centrey = Possibles[i]->Y + HalfBoxSize;
			
			int distance = abs((int)round(TouchEvent.X - centrex)) + abs((int)round(TouchEvent.Y - centrey));
			if (distance < maxdist) {
				box = Possibles[i];
				maxdist = distance;
			}
		}
		
		// if we clicked on nothing, bail out
		if (box == NULL) return;
		
		if (Moves.Contains(box)) {
			if (Moves[Moves.Count - 1] == box) {
				// they selected the last square
				if (Moves.Count == 1) {
					// if this is the first square (ie, the only square), remove it
					Moves.Remove(box);
				} else {
					// otherwise, see if this matches a word!
					string check;
					
					for (int i = 0; i < Moves.Count; ++i) {
						check.append(Moves[i]->Letter);
					}
					
					if (IsWord(check)) {
						for (int i = 0; i < Moves.Count; ++i) {
							Moves[i]->MatchTime = TheGame->TickCount;
						}
						
						int ScoreAdd = (Moves.Count * Moves.Count) * 100;
						ModifyScore(ScoreAdd);
						TheGame->PlaySound("correct");
						++LastColour;
						
						if (LastColour > 3) LastColour = 0;
						
						Moves.Clear();
					}
					
				}
				
			} else {
				// they selected a square other than the last one - remove all moves after this one
				int pos = Moves.IndexOf(box);
				
				for (int i = Moves.Count - 1; i > pos; --i) {
					Moves.RemoveAt(i);
				}
				
				TheGame->PlaySound("wrong3");
			}
		} else {
			if (Moves.Count > 0) {
				BPMiniGame_WordSmash_Letter* last = Moves[Moves.Count - 1];
				
				if (CanMove(box, last)) {
					Moves.Add(box);
					TheGame->PlaySound("gem_select");
				} else {
					// illegal move!
					//	[MessageBox Show:"You can only move to a square that's horizontally or vertically next to your previous move.":"Oops!"];
				}
			} else {
				Moves.Add(box);
				TheGame->PlaySound("gem_select");
			}
		}
	}
}

bool BPMiniGame_WordSmash::CanMove(BPMiniGame_WordSmash_Letter* square1, BPMiniGame_WordSmash_Letter* square2) {
	// return true if this square is adjacent to the last move square
	
	return (abs(square1->X - square2->X) + abs(square1->Y - square2->Y)) == BoxSize;
}

void BPMiniGame_WordSmash::Start() {
	TimeStarted = TheGame->TickCount;
}


void BPMiniGame_WordSmash::Render() {
	sfcBackgroundBottom->Draw(0, -20);
	
	int TimePassed = TheGame->TickCount - TimeStarted;
	TimePassed = 300000 - TimePassed;
	
	if (TimePassed <= 0) {
		if (SuccessTime == -1) {
			SuccessTime = TheGame->TickCount;
			Success();
		}
	}
	
	if (sfcClock == NULL || RedrawClock()) {
		TheGame->AllocString(&sfcClock, TheGame->TicksToTime(TimePassed)->c_str(), LARGE, 80, 47, LEFT);
	}
	
	TheGame->DrawString(sfcClock, WHITE, 235, 10);
	
	TheGame->DrawString(sfcScoreStr, WHITE, 15, 10);
	
	BPMiniGame_WordSmash_Letter* box;
	
	for (int i = 0; i < Boxes.size(); ++i) {
		vector<BPMiniGame_WordSmash_Letter*>* row = Boxes[i];
		
		for (int j = row->size() - 1; j >= 0; --j) {
			box = (*row)[j];
			
			// HACK: There's an unknown bug in this minigame that occasionally puts boxes in the wrong X pos.
			// To reproduce, just keep matching stuff while dragging around on the screen manically.
			// This force-resets the X position for each box to the correct value. What a waste of CPU time!
			if (SelectedRow != row) box->X = LeftIndent + (j * BoxSize);
			
			if (box->MatchTime == -1) {
				if (Moves.Contains(box)) {
					switch (LastColour) {
						case 0:
							sfcLetters[box->Letter]->Draw(box->X + HalfBoxSize, box->Y + HalfBoxSize, 0.0f, 1.0f, (*TheGame->LightRed));
							break;
							
						case 1:
							sfcLetters[box->Letter]->Draw(box->X + HalfBoxSize, box->Y + HalfBoxSize, 0.0f, 1.0f, (*TheGame->Yellow));
							break;
							
						case 2:
							sfcLetters[box->Letter]->Draw(box->X + HalfBoxSize, box->Y + HalfBoxSize, 0.0f, 1.0f, (*TheGame->Green));
							break;
							
						case 3:
							sfcLetters[box->Letter]->Draw(box->X + HalfBoxSize, box->Y + HalfBoxSize, 0.0f, 1.0f, (*TheGame->Cyan));
							break;
					}
				} else {
					sfcLetters[box->Letter]->Draw(box->X + HalfBoxSize, box->Y + HalfBoxSize, 0.0f, 1.0f, (*TheGame->White));
					
					if (box->X < LeftIndent) {
						sfcLetters[box->Letter]->Draw(GameWidth + box->X + HalfBoxSize - LeftIndent - RightIndent, box->Y + HalfBoxSize, 0.0f, 1.0f, (*TheGame->White));
					} else if (box->X > GameWidth - BoxSize - RightIndent) {
						sfcLetters[box->Letter]->Draw(HalfBoxSize + LeftIndent + RightIndent + box->X - GameWidth, box->Y + HalfBoxSize, 0.0f, 1.0f, (*TheGame->White));
					}
				}
			} else {
				float diff = TheGame->TickCount - box->MatchTime;
				
				if (diff <= DisappearTime) {
					float step = diff / DisappearTime; // get a value between 0 and 1
					Colour col = Colour(1.0f, 1.0f, 1.0f, 1 - step);
					
					sfcLetters[box->Letter]->Draw(box->X + HalfBoxSize, box->Y + HalfBoxSize, 0.0f, TheGame->SmoothStep(1.0f, 3.0f, step), col);
				}
			}
		}
	}
	
	// draw the background last to mask the hidden letters
	sfcBackgroundTop->Draw(0, -20);
}

void BPMiniGame_WordSmash::Tick() {
	vector<BPMiniGame_WordSmash_Letter*>* row;
	BPMiniGame_WordSmash_Letter* box;
	BPMiniGame_WordSmash_Letter* copybox;
	
	int MatchTimeout = TheGame->TickCount - DisappearTime;
	
	bool AllStill = true;
	
	for (int i = Boxes.size() - 1; i >= 0; --i) {
		row = Boxes[i];
		
		for (int j = row->size() - 1; j >= 0; --j) {
			box = (*row)[j];
			
			if (box->MatchTime != -1) AllStill = false;
			
			if (box->Y != box->DestY) {
				AllStill = false;
				
				box->YSpeed += 2.0f;
				box->Y += box->YSpeed;
				if (box->Y > box->DestY) {
					box->Y = box->DestY;
					box->YSpeed = 0;
				}
			}
			
			if (box->MatchTime != -1 && box->MatchTime < MatchTimeout) {
				row->erase(row->begin() + j);
				// move all boxes down above me
				
				for (int k = i - 1; k >= 0; --k) {
					copybox = (*Boxes[k])[j];
					Boxes[k]->erase(Boxes[k]->begin() + j);
					Boxes[k + 1]->insert(Boxes[k + 1]->begin() + j, copybox);
					copybox->DestY = 57 + ((k + 1) * BoxSize);
				}
				
				BPMiniGame_WordSmash_Letter* newbox = new BPMiniGame_WordSmash_Letter();
				newbox->X = LeftIndent + j * BoxSize;
				newbox->Y = -BoxSize;
				newbox->DestY = 57;
				newbox->Letter = GetLetter();
				Boxes[0]->insert(Boxes[0]->begin() + j, newbox);
			}
		}
	}
	
	if (AllStill) {
		Locked = false;
		JustMoved = false;
	} else {
		Locked = true;
	}
}

bool BPMiniGame_WordSmash::IsWord(string &word) {
	return Words->Contains(word);
}


string BPMiniGame_WordSmash::GetLetter() {
	int freq_a = 8167;
	int freq_b = freq_a + 1492;
	int freq_c = freq_b + 2782;
	int freq_d = freq_c + 4253;
	int freq_e = freq_d + 12702;
	int freq_f = freq_e + 2228;
	int freq_g = freq_f + 2015;
	int freq_h = freq_g + 6094;
	int freq_i = freq_h + 6966;
	int freq_j = freq_i + 153;
	int freq_k = freq_j + 772;
	int freq_l = freq_k + 4025;
	int freq_m = freq_l + 2406;
	int freq_n = freq_m + 6749;
	int freq_o = freq_n + 7507;
	int freq_p = freq_o + 1929;
	int freq_q = freq_p + 95;
	int freq_r = freq_q + 5987;
	int freq_s = freq_r + 6327;
	int freq_t = freq_s + 9056;
	int freq_u = freq_t + 2758;
	int freq_v = freq_u + 978;
	int freq_w = freq_v + 2360;
	int freq_x = freq_w + 150;
	int freq_y = freq_x + 1974;
	//	int freq_z = freq_y + 74;
	
	int random = TheGame->RandomRange(0, 100000);
	
	if (random < freq_a) {
		return string("a");
	} else if (random < freq_b) {
		return string("b");
	} else if (random < freq_c) {
		return string("c");
	} else if (random < freq_d) {
		return string("d");
	} else if (random < freq_e) {
		return string("e");
	} else if (random < freq_f) {
		return string("f");
	} else if (random < freq_g) {
		return string("g");
	} else if (random < freq_h) {
		return string("h");
	} else if (random < freq_i) {
		return string("i");
	} else if (random < freq_j) {
		return string("j");
	} else if (random < freq_k) {
		return string("k");
	} else if (random < freq_l) {
		return string("l");
	} else if (random < freq_m) {
		return string("m");
	} else if (random < freq_n) {
		return string("n");
	} else if (random < freq_o) {
		return string("o");
	} else if (random < freq_p) {
		return string("p");
	} else if (random < freq_q) {
		return string("qu");
	} else if (random < freq_r) {
		return string("r");
	} else if (random < freq_s) {
		return string("s");
	} else if (random < freq_t) {
		return string("t");
	} else if (random < freq_u) {
		return string("u");
	} else if (random < freq_v) {
		return string("v");
	} else if (random < freq_w) {
		return string("w");
	} else if (random < freq_x) {
		return string("x");
	} else if (random < freq_y) {
		return string("y");
	} else {
		return string("z");
	}
	
	return string("");
}

void BPMiniGame_WordSmash::ModifyScore(int adjust) {
	Score += adjust;
	
	if (Score < 0) Score = 0;
	
	SetScore();
}

void BPMiniGame_WordSmash::SetScore() {
	if (Score > 0) {
		ostringstream score;
		score << "Score: " << TheGame->SeparateThousands(Score);
		TheGame->AllocString(&sfcScoreStr, score.str().c_str(), LARGE, 250, 50, LEFT);
	} else {
		TheGame->AllocString(&sfcScoreStr, "Score: 0", LARGE, 250, 50, LEFT);
	}
}
