/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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.
 *
 * 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 "bbvs/minigames/bbairguitar.h"

#include "common/savefile.h"
#include "common/translation.h"

#include "gui/message.h"
#include "gui/filebrowser-dialog.h"

namespace Bbvs {

static const char * const kNoteSoundFilenames[] = {
	"a.aif", "a#.aif", "b.aif", "c.aif", "c#.aif",
	"d.aif", "d#.aif", "e.aif", "f.aif", "f#.aif",
	"g.aif", "g#.aif", "a_oct.aif"
};

static const uint kNoteSoundFilenamesCount = ARRAYSIZE(kNoteSoundFilenames);

static const char * const kPatchDirectories[] = {
	"rock", "burp", "fart"
};

static const uint kPatchDirectoriesCount = ARRAYSIZE(kPatchDirectories);

static const BBPoint kPianoKeyArea1[] = {{29, 192}, {38, 192}, {38, 222}, {41, 222}, {41, 239}, {29, 239}};
static const BBPoint kPianoKeyArea2[] = {{38, 192}, {43, 192}, {43, 222}, {38, 222}};
static const BBPoint kPianoKeyArea3[] = {{43, 192}, {49, 192}, {49, 222}, {52, 222}, {52, 239}, {41, 239}, {41, 222}, {43, 222}};
static const BBPoint kPianoKeyArea4[] = {{49, 192}, {54, 192}, {54, 222}, {49, 222}};
static const BBPoint kPianoKeyArea5[] = {{54, 192}, {63, 192}, {63, 239}, {52, 239}, {52, 222}, {54, 222}};
static const BBPoint kPianoKeyArea6[] = {{63, 192}, {71, 192}, {71, 222}, {74, 222}, {74, 239}, {63, 239}};
static const BBPoint kPianoKeyArea7[] = {{71, 192}, {76, 192}, {76, 222}, {71, 222}};
static const BBPoint kPianoKeyArea8[] = {{76, 192}, {82, 192}, {82, 222}, {85, 222}, {85, 239}, {74, 239}, {74, 222}, {76, 222}};
static const BBPoint kPianoKeyArea9[] = {{82, 192}, {87, 192}, {87, 222}, {82, 222}};
static const BBPoint kPianoKeyArea10[] = {{87, 192}, {94, 192}, {94, 222}, {96, 222}, {96, 239}, {85, 239}, {85, 222}, {87, 222}};
static const BBPoint kPianoKeyArea11[] = {{94, 192}, {99, 192}, {99, 222}, {94, 222}};
static const BBPoint kPianoKeyArea12[] = {{99, 192}, {107, 192}, {107, 239}, {96, 239}, {96, 222}, {99, 222}};
static const BBPoint kPianoKeyArea13[] = {{107, 192}, {118, 192}, {118, 239}, {107, 239}};

static const BBPolygon kPianoKeyAreas[] = {
	{kPianoKeyArea1, ARRAYSIZE(kPianoKeyArea1)},
	{kPianoKeyArea2, ARRAYSIZE(kPianoKeyArea2)},
	{kPianoKeyArea3, ARRAYSIZE(kPianoKeyArea3)},
	{kPianoKeyArea4, ARRAYSIZE(kPianoKeyArea4)},
	{kPianoKeyArea5, ARRAYSIZE(kPianoKeyArea5)},
	{kPianoKeyArea6, ARRAYSIZE(kPianoKeyArea6)},
	{kPianoKeyArea7, ARRAYSIZE(kPianoKeyArea7)},
	{kPianoKeyArea8, ARRAYSIZE(kPianoKeyArea8)},
	{kPianoKeyArea9, ARRAYSIZE(kPianoKeyArea9)},
	{kPianoKeyArea10, ARRAYSIZE(kPianoKeyArea10)},
	{kPianoKeyArea11, ARRAYSIZE(kPianoKeyArea11)},
	{kPianoKeyArea12, ARRAYSIZE(kPianoKeyArea12)},
	{kPianoKeyArea13, ARRAYSIZE(kPianoKeyArea13)},
};

static const BBPoint kObjPoints[] = {
	{161, 189}, {269, 189}, {161, 208}, {279, 208}, {172, 208},
	{141, 224}, {257, 191}, {257, 199}, {148, 223}, {124, 224},
	{ 29, 192}, {182, 220}, {245, 220}, {269, 220}, {161, 220},
	{203, 220}, {224, 220}, {123, 189}, {123, 199}, {123, 209},
	{134, 224}, { 29, 185}, {124, 224}, {226, 127}, {226, 127},
	{209, 141}, {244, 141}, {226, 127}, { 99, 107}, { 99, 107},
	{ 76, 137}, {118, 136}, { 99, 107}, {195, 104}, {100,  78}
};

static const MinigameBbAirGuitar::PianoKeyInfo kPianoKeyInfos[] = {
	{ 30, 192, 0},
	{ 38, 192, 5},
	{ 41, 192, 1},
	{ 49, 192, 5},
	{ 52, 192, 2},
	{ 63, 192, 3},
	{ 71, 192, 5},
	{ 74, 192, 1},
	{ 82, 192, 5},
	{ 85, 192, 1},
	{ 94, 192, 5},
	{ 96, 192, 2},
	{107, 192, 4}
};

static const Rect kRect2 = {29, 189, 290, 239};
static const Rect kPianoRect = {29, 192, 118, 239};

static const Rect kPlayerButtonRects[] = {
	{123, 189, 145, 199},
	{123, 199, 145, 209},
	{123, 209, 145, 220},
	{148, 223, 156, 236},
	{161, 189, 182, 205},
	{161, 208, 171, 218},
	{161, 220, 182, 231},
	{182, 220, 203, 231},
	{203, 220, 224, 231},
	{224, 220, 245, 231},
	{245, 220, 266, 231},
	{269, 220, 290, 231},
	{269, 189, 290, 205},
	{279, 208, 290, 218}
};

static const BBPoint kPointsTbl1[] = {
	{196, 191},	{202, 191}, {207, 191}, {212, 191}, {217, 191},
	{223, 191}, {228, 191}, {233, 191}, {238, 191}, {244, 191},
	{249, 191}
};

static const BBPoint kPointsTbl2[] = {
	{196, 199}, {202, 199}, {207, 199}, {212, 199}, {217, 199},
	{223, 199}, {228, 199}, {233, 199}, {238, 199}, {244, 199},
	{249, 199}
};

static const struct { int frameIndex; byte flag; } kNoteFrameTbl[13] = {
	{2, 0}, {2, 1}, {3, 0}, {3, 1}, {4, 0},
	{5, 0}, {5, 1}, {6, 0}, {6, 1}, {0, 0},
	{0, 1}, {1, 0}, {2, 0}
};

const int kTrackBarMinX = 172;
const int kTrackBarMaxX = 272;

bool MinigameBbAirGuitar::ptInRect(const Rect *r, int x, int y) {
	return r && Common::Rect(r->left, r->top, r->right, r->bottom).contains(x, y);
}

bool MinigameBbAirGuitar::ptInPoly(const BBPolygon *poly, int x, int y) {
	if (!poly)
		return false;
	const BBPoint *points = poly->points;
	int pointsCount = poly->pointsCount;
	bool result = false;
	if (pointsCount > 0)
		for (int i = 0, j = pointsCount - 1; i < pointsCount; j = i++)
			if (((points[i].y > y) != (points[j].y > y)) &&
				(x < (points[j].x - points[i].x) * (y - points[i].y) /
				(points[j].y - points[i].y) + points[i].x))
				result = !result;
	return result;
}

void MinigameBbAirGuitar::buildDrawList(DrawList &drawList) {
	switch (_gameState) {
	case 0:
		buildDrawList0(drawList);
		break;
	case 1:
		buildDrawList1(drawList);
		break;
	}
}

void MinigameBbAirGuitar::buildDrawList0(DrawList &drawList) {

	drawList.add(_objects[0].anim->frameIndices[0], _objects[0].x, _objects[0].y, 2000);

	for (int i = 1; i < kMaxObjectsCount; ++i) {
		Obj *obj = &_objects[i];
		if (obj->kind)
			drawList.add(obj->anim->frameIndices[obj->frameIndex], obj->x, obj->y, obj->y + 16);
	}

	if (_titleScreenSpriteIndex> 0)
		drawList.add(_titleScreenSpriteIndex, 0, 0, 0);

}

void MinigameBbAirGuitar::buildDrawList1(DrawList &drawList) {

	for (int i = 0; i < kMaxObjectsCount; ++i) {
		Obj *obj = &_objects[i];
		if (obj->kind)
			drawList.add(obj->anim->frameIndices[obj->frameIndex], obj->x, obj->y, 255 - i);
	}

	if (_movingTrackBar) {
		_trackBarX = _trackBarMouseX;
	} else if (_totalTrackLength > 0) {
		_trackBarX = 100 * _currTrackPos / _totalTrackLength + kTrackBarMinX;
	} else {
		_trackBarX = kTrackBarMinX;
	}

	if (_trackBarX > kTrackBarMaxX)
		_trackBarX = kTrackBarMaxX;

	_trackBarThumbRect.top = 208;
	_trackBarThumbRect.bottom = 218;
	_trackBarThumbRect.left = _trackBarX;
	_trackBarThumbRect.right = _trackBarX + 6;

	drawList.add(_objects[5].anim->frameIndices[0], _trackBarX, 208, 100);

	if (_playerMode != 0) {
		for (int i = 36; i < _vuMeterLeft2 + 36; ++i) {
			int frameIndex = 0;
			if (i >= 45)
				frameIndex = 3;
			else if (i >= 43)
				frameIndex = 2;
			else if (i >= 41)
				frameIndex = 1;
			drawList.add(_objects[i].anim->frameIndices[frameIndex], kPointsTbl1[i - 36].x, kPointsTbl1[i - 36].y, 254);
		}
		for (int i = 47; i < _vuMeterRight2 + 47; ++i) {
			int frameIndex = 0;
			if (i >= 56)
				frameIndex = 3;
			else if (i >= 54)
				frameIndex = 2;
			else if (i >= 52)
				frameIndex = 1;
			drawList.add(_objects[i].anim->frameIndices[frameIndex], kPointsTbl2[i - 47].x, kPointsTbl2[i - 47].y, 254);
		}
	}

	if (_backgroundSpriteIndex > 0)
		drawList.add(_backgroundSpriteIndex, 0, 0, 0);

}

void MinigameBbAirGuitar::drawSprites() {
	DrawList drawList;
	buildDrawList(drawList);
	_vm->_screen->drawDrawList(drawList, _spriteModule);
	_vm->_screen->copyToScreen();
}

void MinigameBbAirGuitar::initObjs() {
	for (int i = 0; i < kMaxObjectsCount; ++i)
		_objects[i].kind = 0;
}

MinigameBbAirGuitar::Obj *MinigameBbAirGuitar::getFreeObject() {
	for (int i = 0; i < kMaxObjectsCount; ++i)
		if (_objects[i].kind == 0)
			return &_objects[i];
	return 0;
}

void MinigameBbAirGuitar::initObjects() {
	switch (_gameState) {
	case 0:
		initObjects0();
		break;
	case 1:
		initObjects1();
		break;
	}
}

void MinigameBbAirGuitar::initObjects0() {
	_objects[0].anim = getAnimation(0);
	_objects[0].frameIndex = 0;
	_objects[0].ticks = getAnimation(0)->frameTicks[0];
	_objects[0].x = 160;
	_objects[0].y = 120;
	_objects[0].kind = 1;
	_objects[1].anim = getAnimation(37);
	_objects[1].frameIndex = 0;
	_objects[1].ticks = getAnimation(37)->frameTicks[0];
	_objects[1].x = 40;
	_objects[1].y = 240;
	_objects[1].kind = 2;
	_objects[2].anim = getAnimation(36);
	_objects[2].frameIndex = 0;
	_objects[2].ticks = getAnimation(36)->frameTicks[0];
	_objects[2].x = 280;
	_objects[2].y = 240;
	_objects[2].kind = 2;

}

void MinigameBbAirGuitar::initObjects1() {

	for (int i = 0; i < 60; ++i)
		_objects[i].kind = 0;

	_objects[0].kind = 0;
	_objects[0].kind = 1;
	_objects[0].anim = getAnimation(0);
	_objects[0].ticks = getAnimation(0)->frameTicks[0];
	_objects[1].anim = getAnimation(1);
	_objects[1].ticks = getAnimation(1)->frameTicks[0];
	_objects[2].anim = getAnimation(2);
	_objects[2].ticks = getAnimation(2)->frameTicks[0];
	_objects[3].anim = getAnimation(3);
	_objects[3].ticks = getAnimation(3)->frameTicks[0];
	_objects[4].anim = getAnimation(4);
	_objects[4].ticks = getAnimation(4)->frameTicks[0];
	_objects[5].anim = getAnimation(5);
	_objects[5].ticks = getAnimation(5)->frameTicks[0];
	_objects[6].anim = getAnimation(6);
	_objects[6].ticks = getAnimation(6)->frameTicks[0];
	_objects[7].anim = getAnimation(8);
	_objects[7].ticks = getAnimation(8)->frameTicks[0];
	_objects[8].anim = getAnimation(9);
	_objects[8].ticks = getAnimation(9)->frameTicks[0];
	_objects[9].anim = getAnimation(10);
	_objects[9].ticks = getAnimation(10)->frameTicks[0];
	_objects[10].anim = getAnimation(11);
	_objects[10].ticks = getAnimation(11)->frameTicks[0];
	_objects[11].anim = getAnimation(12);
	_objects[11].ticks = getAnimation(12)->frameTicks[0];
	_objects[12].anim = getAnimation(13);
	_objects[12].ticks = getAnimation(13)->frameTicks[0];
	_objects[13].anim = getAnimation(14);
	_objects[13].ticks = getAnimation(14)->frameTicks[0];
	_objects[14].anim = getAnimation(15);
	_objects[14].ticks = getAnimation(15)->frameTicks[0];
	_objects[15].anim = getAnimation(16);
	_objects[15].ticks = getAnimation(16)->frameTicks[0];
	_objects[16].anim = getAnimation(17);
	_objects[16].ticks = getAnimation(17)->frameTicks[0];
	_objects[17].anim = getAnimation(18);
	_objects[17].ticks = getAnimation(18)->frameTicks[0];
	_objects[18].anim = getAnimation(19);
	_objects[18].ticks = getAnimation(19)->frameTicks[0];
	_objects[19].anim = getAnimation(20);
	_objects[19].ticks = getAnimation(20)->frameTicks[0];
	_objects[20].anim = getAnimation(21);
	_objects[20].ticks = getAnimation(21)->frameTicks[0];
	_objects[21].anim = getAnimation(11);
	_objects[21].ticks = getAnimation(11)->frameTicks[0];
	_objects[22].anim = getAnimation(22);
	_objects[22].ticks = getAnimation(22)->frameTicks[0];
	_objects[23].anim = getAnimation(23);
	_objects[23].ticks = getAnimation(23)->frameTicks[0];
	_objects[24].anim = getAnimation(24);
	_objects[24].ticks = getAnimation(24)->frameTicks[0];
	_objects[25].anim = getAnimation(25);
	_objects[25].ticks = getAnimation(25)->frameTicks[0];
	_objects[26].anim = getAnimation(26);
	_objects[26].ticks = getAnimation(26)->frameTicks[0];
	_objects[27].anim = getAnimation(27);
	_objects[27].ticks = getAnimation(27)->frameTicks[0];
	_objects[28].anim = getAnimation(28);
	_objects[28].ticks = getAnimation(28)->frameTicks[0];
	_objects[29].anim = getAnimation(29);
	_objects[29].ticks = getAnimation(29)->frameTicks[0];
	_objects[30].anim = getAnimation(30);
	_objects[30].ticks = getAnimation(30)->frameTicks[0];
	_objects[31].anim = getAnimation(31);
	_objects[31].ticks = getAnimation(31)->frameTicks[0];
	_objects[32].anim = getAnimation(32);
	_objects[32].ticks = getAnimation(32)->frameTicks[0];
	_objects[33].anim = getAnimation(33);
	_objects[33].ticks = getAnimation(33)->frameTicks[0];
	_objects[34].anim = getAnimation(34);
	_objects[34].ticks = getAnimation(34)->frameTicks[0];
	_objects[35].anim = getAnimation(35);
	_objects[35].ticks = getAnimation(35)->frameTicks[0];

	for (int i = 36; i <= 57; ++i) {
		_objects[i].anim = getAnimation(7);
		_objects[i].ticks = getAnimation(7)->frameTicks[0];
	}

	for (int i = 1; i <= 35; ++i) {
		_objects[i].x = kObjPoints[i - 1].x;
		_objects[i].y = kObjPoints[i - 1].y;
	}

	_objects[22].kind = 1;
	_objects[6].kind = 1;
	_objects[26].kind = 1;
	_objects[26].frameIndex = 3;
	_objects[27].kind = 1;
	_objects[27].frameIndex = 3;
	_objects[31].kind = 1;
	_objects[31].frameIndex = 3;
	_objects[32].kind = 1;
	_objects[32].frameIndex = 3;
	_objects[28].kind = 1;
	_objects[33].kind = 1;
	_objects[34].kind = 1;
	_objects[35].kind = 1;

	_track[0].noteNum = -1;
	stop();
	changePatch(0);

}

bool MinigameBbAirGuitar::updateStatus(int mouseX, int mouseY, uint mouseButtons) {
	switch (_gameState) {
	case 0:
		return updateStatus0(mouseX, mouseY, mouseButtons);
	case 1:
		return updateStatus1(mouseX, mouseY, mouseButtons);
	}
	return false;
}

bool MinigameBbAirGuitar::updateStatus0(int mouseX, int mouseY, uint mouseButtons) {

	if (mouseButtons & kAnyButtonDown) {
		stopSound(1);
		_rockTunePlaying = false;
		_gameState = 1;
		initObjects();
		_gameTicks = 0;
	} else {

		if (!_rockTunePlaying) {
			_rockTunePlaying = true;
			playSound(1, true);
		}

		_objects[0].x = mouseX;
		_objects[0].y = mouseY;

		for (int i = 1; i < kMaxObjectsCount; ++i) {
			Obj *obj = &_objects[i];
			if (obj->kind && --obj->ticks == 0) {
				++obj->frameIndex;
				if (obj->frameIndex >= obj->anim->frameCount)
					obj->frameIndex = 0;
				obj->ticks = obj->anim->frameTicks[obj->frameIndex];
			}
		}

	}

  	return true;
}

bool MinigameBbAirGuitar::updateStatus1(int mouseX, int mouseY, uint mouseButtons) {

	int currTicks = _vm->_system->getMillis();

	if (_playerMode == 1 && _track[_trackIndex].ticks <= currTicks - _noteStartTime) {
		noteOff(_track[_trackIndex].noteNum);
		if (_trackIndex < _trackCount && _track[++_trackIndex].noteNum != -1)
			noteOn(_track[_trackIndex].noteNum);
		else
			stop();
	}

	if (_vuMeterLeft1 - 2 <= _vuMeterLeft2) {
		if (_vuMeterLeft1 + 1 >= _vuMeterLeft2) {
			int incr = MIN(_vm->getRandom(4), 2) - 1;
			if (incr < 0 && _vuMeterLeft2 == 0)
				incr = -incr;
			if (incr > 0 && _vuMeterLeft2 == 11)
				incr = -incr;
			_vuMeterLeft2 += incr;
		} else {
			--_vuMeterLeft2;
		}
	} else {
		++_vuMeterLeft2;
	}

	if (_vuMeterRight1 - 2 <= _vuMeterRight2) {
		if (_vuMeterRight1 + 1 >= _vuMeterRight2) {
			int incr = MIN(_vm->getRandom(4), 2) - 1;
			if (incr < 0 && _vuMeterRight2 == 0)
				incr = -incr;
			if (incr > 0 && _vuMeterRight2 == 11)
				incr = -incr;
			_vuMeterRight2 += incr;
		} else {
			--_vuMeterRight2;
		}
	} else {
		++_vuMeterRight2;
	}

	if (_resetAnims && _vm->_system->getMillis() - _noteStartTime >= 1000)
		resetObjs();

	_objects[0].x = mouseX;
	_objects[0].y = mouseY;

	_trackBarMouseX = CLIP(mouseX, kTrackBarMinX, kTrackBarMaxX);

	bool checkClick = false;

	if (mouseButtons & kAnyButtonClicked) {
		checkClick = true;
	} else if (!(mouseButtons & kAnyButtonDown)) {
		afterButtonReleased();
	} else if (!_movingTrackBar && ((_currButtonNum >= 14 && ptInPoly(_currPianoKeyArea, mouseX, mouseY)) || ptInRect(_currPlayerButtonRect, mouseX, mouseY))) {
		if (_currButtonNum == 5 && _trackIndex > 0) {
			--_trackIndex;
			calcTotalTicks2();
		} else if (_currButtonNum == 13 && _trackIndex < _trackCount) {
			++_trackIndex;
			calcTotalTicks2();
		}
	} else if (!_movingTrackBar)
		checkClick = true;

	if (checkClick) {

		afterButtonReleased();
		_objects[0].frameIndex = 1;

		if (ptInRect(&kRect2, mouseX, mouseY)) {

			if (_playerMode != 1 && ptInRect(&kPianoRect, mouseX, mouseY)) {
				for (int i = 0; i <= 12; ++i) {
					if (ptInPoly(&kPianoKeyAreas[i], mouseX, mouseY)) {
						_currButtonNum = i + 14;
						_currPianoKeyArea = &kPianoKeyAreas[i];
						_objects[11].kind = 1;
						_objects[11].x = kPianoKeyInfos[i].x;
						_objects[11].y = kPianoKeyInfos[i].y;
						_objects[11].frameIndex = kPianoKeyInfos[i].frameIndex;
						noteOn(i);
						break;
					}
				}
			} else if (_playerMode != 1 && ptInRect(&_trackBarThumbRect, mouseX, mouseY)) {
				_movingTrackBar = true;
			} else {

				int playerButtonNum = -1;
				for (int i = 0; i < 14; ++i) {
					if (ptInRect(&kPlayerButtonRects[i], mouseX, mouseY)) {
						playerButtonNum = i;
						break;
					}
				}

				if (playerButtonNum >= 0) {
					_currButtonNum = playerButtonNum;
					_currPlayerButtonRect = &kPlayerButtonRects[playerButtonNum];

					switch (playerButtonNum) {

					case 0:
						if (_playerMode == 0) {
							changePatch(0);
							_currFrameIndex = &_objects[18 + 0].frameIndex;
							*_currFrameIndex = 0;
						}
						break;

					case 1:
						if (_playerMode == 0) {
							changePatch(1);
							_currFrameIndex = &_objects[18 + 1].frameIndex;
							*_currFrameIndex = 0;
						}
						break;

					case 2:
						if (_playerMode == 0) {
							changePatch(2);
							_currFrameIndex = &_objects[18 + 2].frameIndex;
							*_currFrameIndex = 0;
						}
						break;

					case 3:
						_btn3KindToggle = !_btn3KindToggle;
						_objects[9].kind = _btn3KindToggle ? 0 : 1;
						_objects[22].frameIndex = _btn3KindToggle ? 0 : 1;
						break;

					case 4:
						if (_playerMode == 0) {
							_objects[1].kind = 1;
							_currFrameIndex = &_objects[1].frameIndex;
							_objects[1].frameIndex = 0;
						}
						break;

					case 5:
						if (_playerMode == 0) {
							if (_trackIndex > 0)
								--_trackIndex;
							_objects[3].kind = 1;
							calcTotalTicks2();
						}
						break;

					case 6:
						stop();
						_currFrameIndex = &_objects[15].frameIndex;
						_objects[15].frameIndex = 0;
						break;

					case 7:
						if (_playerMode == 0) {
							play();
							_currFrameIndex = &_objects[12].frameIndex;
							_objects[12].frameIndex = 0;
						}
						break;

					case 8:
						if (_playerMode == 0) {
							_trackIndex = 0;
							_objects[16].kind = 1;
							calcTotalTicks2();
						}
						break;

					case 9:
						if (_playerMode == 0) {
							_trackIndex = _trackCount;
							_objects[17].kind = 1;
							calcTotalTicks2();
						}
						break;

					case 10:
						if (_playerMode == 0) {
							record();
							_currFrameIndex = &_objects[13].frameIndex;
							_objects[13].frameIndex = 0;
						}
						break;

					case 11:
						if (_playerMode == 0) {
							setPlayerMode3();
							_currFrameIndex = &_objects[14].frameIndex;
							_objects[14].frameIndex = 0;
						}
						break;

					case 12:
						if (_playerMode == 0) {
							_objects[2].kind = 1;
							_currFrameIndex = &_objects[2].frameIndex;
							_objects[2].frameIndex = 0;
						}
						break;

					case 13:
						if (_playerMode == 0) {
							if (_trackIndex < _trackCount)
								++_trackIndex;
							_objects[4].kind = 1;
							calcTotalTicks2();
						}
						break;

					}
				}
			}
		}
	}

	if (_playerMode != 0) {
		_currTrackPos = currTicks + _actionStartTrackPos - _actionStartTime;
		if (_currTrackPos > _actionTrackPos && _playerMode != 1) {
			if (_currTrackPos >= 15000) {
				_currTrackPos = 15000;
				_actionTrackPos = 15000;
				stop();
			} else {
				_actionTrackPos = currTicks + _actionStartTrackPos - _actionStartTime;
			}
		}
	}

	if (_buttonClickTicks + 1000 < currTicks)
		_buttonClickTicks = currTicks;

	int newKind = _buttonClickTicks + 500 < currTicks ? 1 : 0;

	switch (_playerMode) {

	case 1:
		if (_currButtonNum == 7) {
			_objects[12].kind = 1;
			_objects[12].frameIndex = 0;
		} else {
			_objects[12].kind = newKind;
			_objects[12].frameIndex = 1;
		}
		break;

	case 2:
		if (_currButtonNum == 10) {
			_objects[13].kind = 1;
			_objects[13].frameIndex = 0;
		} else {
			_objects[13].kind = newKind;
			_objects[13].frameIndex = 1;
		}
		break;

	case 3:
		if (_currButtonNum == 11) {
			_objects[14].kind = 1;
			_objects[14].frameIndex = 0;
		} else {
			_objects[14].kind = newKind;
			_objects[14].frameIndex = 1;
		}
		break;

	}

	updateObjs();

	return true;
}

void MinigameBbAirGuitar::updateObjs() {
	for (int i = 24; i <= 33; ++i) {
		Obj *obj = &_objects[i];
		if (obj->kind && --obj->ticks == 0) {
			if (obj->frameIndex + 1 >= obj->anim->frameCount) {
				obj->ticks = -1;
			} else {
				++obj->frameIndex;
				obj->ticks = obj->anim->frameTicks[obj->frameIndex];
			}
		}
	}
}

bool MinigameBbAirGuitar::run(bool fromMainGame) {

	memset(_objects, 0, sizeof(_objects));

	_modified = false;
	_currPatchNum = -1;
	_btn3KindToggle = 0;
	_currButtonNum = 27;
	_actionStartTime = 0;
	_currFrameIndex = 0;
	_currPlayerButtonRect = 0;
	_currPianoKeyArea = 0;
	_trackCount = 0;
	_trackIndex = 0;
	_totalTrackLength = 0;
	_actionTrackPos = 0;
	_noteStartTime = 0;
	_actionStartTrackPos = 0;
	_trackBarX = kTrackBarMinX;
	_currTrackPos = 0;
	_currNoteNum = -2;
	_resetAnims = false;
	_vuMeterLeft2 = 0;
	_vuMeterRight2 = 0;
	_vuMeterLeft1 = 0;
	_vuMeterRight1 = 0;
	_rockTunePlaying = false;

	_backgroundSpriteIndex = 97;
	_titleScreenSpriteIndex = 98;

	_fromMainGame = fromMainGame;

	_gameState = 0;
	_gameTicks = 0;
	_gameResult = false;
	_gameDone = false;
	initObjects();

	_spriteModule = new SpriteModule();
	_spriteModule->load("bbairg/bbairg.000");

	Palette palette = _spriteModule->getPalette();
	_vm->_screen->setPalette(palette);

	loadSounds();

	while (!_vm->shouldQuit() &&!_gameDone) {
		_vm->updateEvents();
		update();
	}

	_vm->_sound->unloadSounds();

	delete _spriteModule;

	return _gameResult;
}

void MinigameBbAirGuitar::update() {

	int inputTicks;

	if (_gameTicks > 0) {
		int currTicks = _vm->_system->getMillis();
		inputTicks = 3 * (currTicks - _gameTicks) / 50;
		_gameTicks = currTicks - (currTicks - _gameTicks - 50 * inputTicks / 3);
	} else {
		inputTicks = 1;
		_gameTicks = _vm->_system->getMillis();
	}

	if (_vm->_keyCode == Common::KEYCODE_ESCAPE) {
		_gameDone = querySaveModifiedTracks();
		return;
	}

	if (inputTicks == 0)
		return;

	bool done;

	do {
		done = !updateStatus(_vm->_mouseX, _vm->_mouseY, _vm->_mouseButtons);
		_vm->_mouseButtons &= ~kLeftButtonClicked;
		_vm->_mouseButtons &= ~kRightButtonClicked;
		_vm->_keyCode = Common::KEYCODE_INVALID;
	} while (--inputTicks && _gameTicks > 0 && !done);

	drawSprites();

	_vm->_system->delayMillis(10);

}

void MinigameBbAirGuitar::play() {
	if (_track[_trackIndex].noteNum != -1) {
		_playerMode = 1;
		_objects[7].kind = 1;
		_objects[8].kind = 0;
		_objects[15].kind = 0;
		_actionStartTime = _vm->_system->getMillis();
		_actionStartTrackPos = _currTrackPos;
		noteOn(_track[_trackIndex].noteNum);
	}
}


void MinigameBbAirGuitar::record() {
	_playerMode = 2;
	_objects[7].kind = 1;
	_objects[8].kind = 0;
	_objects[15].kind = 0;
	_totalTrackLength = 15000;
	_actionStartTime = _vm->_system->getMillis();
	_actionStartTrackPos = _currTrackPos;
	_noteStartTime = _vm->_system->getMillis();
	_actionTrackPos = _currTrackPos;
	_trackCount = _trackIndex;
	_vuMeterRight1 = 0;
	_vuMeterRight2 = 0;
	_vuMeterLeft1 = 0;
	_vuMeterLeft2 = 0;
	_modified = true;
	_track[_trackIndex].noteNum = -2;
}

void MinigameBbAirGuitar::setPlayerMode3() {
	_playerMode = 3;
	_objects[7].kind = 1;
	_objects[8].kind = 0;
	_objects[15].kind = 0;
	_totalTrackLength = 15000;
	_actionStartTime = _vm->_system->getMillis();
	_actionStartTrackPos = _currTrackPos;
	_noteStartTime = _vm->_system->getMillis();
	_actionTrackPos = _currTrackPos;
	_trackCount = _trackIndex;
	_vuMeterRight1 = 0;
	_vuMeterRight2 = 0;
	_vuMeterLeft1 = 0;
	_vuMeterLeft2 = 0;
	_modified = true;
	_track[_trackIndex].noteNum = -2;
}

void MinigameBbAirGuitar::stop() {
	noteOff(_currNoteNum);
	if (_playerMode == 2 || _playerMode == 3) {
		_totalTrackLength = _actionTrackPos;
		_track[_trackCount].noteNum = -1;
	}
	_playerMode = 0;
	_objects[7].kind = 0;
	_objects[8].kind = 1;
	_objects[15].kind = 1;
	_objects[15].frameIndex = 1;
	_objects[12].kind = 0;
	_objects[13].kind = 0;
	_objects[14].kind = 0;
	resetObjs();
}

void MinigameBbAirGuitar::changePatch(int patchNum) {

	resetObjs();

	if (patchNum == -1 || patchNum != _currPatchNum)
		_currPatchNum = -1;

	_objects[20].kind = 0;
	_objects[19].kind = _objects[20].kind;
	_objects[18].kind = _objects[19].kind;
	_objects[patchNum + 18].kind = 1;
	_objects[patchNum + 18].frameIndex = 1;
	_objects[6].frameIndex = patchNum;
	_currPatchNum = patchNum;
}

void MinigameBbAirGuitar::afterButtonReleased() {
	if (_movingTrackBar) {
		_movingTrackBar = false;
		_currTrackPos = _totalTrackLength * (_trackBarX - kTrackBarMinX) / 100;
		calcTotalTicks1();
	} else {
		switch (_currButtonNum) {
		case 0:
		case 1:
		case 2:
			*_currFrameIndex = 1;
			break;
		case 4:
			*_currFrameIndex = 1;
			loadTracks();
			_objects[1].kind = 0;
			break;
		case 5:
			_objects[3].kind = 0;
			break;
		case 6:
			*_currFrameIndex = 1;
			break;
		case 7:
			*_currFrameIndex = 1;
			break;
		case 8:
			_objects[16].kind = 0;
			break;
		case 9:
			_objects[17].kind = 0;
			break;
		case 10:
			*_currFrameIndex = 1;
			break;
		case 11:
			*_currFrameIndex = 1;
			break;
		case 12:
			*_currFrameIndex = 1;
			saveTracks();
			_objects[2].kind = 0;
			break;
		case 13:
			_objects[4].kind = 0;
			break;
		case 14:
		case 15:
		case 16:
		case 17:
		case 18:
		case 19:
		case 20:
		case 21:
		case 22:
		case 23:
		case 24:
		case 25:
		case 26:
			noteOff(_currButtonNum - 14);
		break;
		}
	}

	_objects->frameIndex = 0;
	_currPlayerButtonRect = 0;
	_currPianoKeyArea = 0;
	_currButtonNum = 27;
}

void MinigameBbAirGuitar::calcTotalTicks2() {
	_currTrackPos = 0;
	for (int i = 0; i < _trackIndex; ++i)
		_currTrackPos += _track[i].ticks;
}

void MinigameBbAirGuitar::calcTotalTicks1() {
	int totalTicks = 0;
	// TODO Try to clean this up
	_trackIndex = 0;
	if (_track[0].ticks <= _currTrackPos) {
		do {
			totalTicks += _track[_trackIndex].ticks;
			if (_trackIndex >= _trackCount)
				break;
			++_trackIndex;
		} while (totalTicks + _track[_trackIndex].ticks <= _currTrackPos);
	}
	_currTrackPos = totalTicks;
}

void MinigameBbAirGuitar::noteOn(int noteNum) {

	if (_currNoteNum != -2) {
		if (noteNum == _currNoteNum)
			return;
		noteOff(_currNoteNum);
	}

	if (noteNum == -2) {
		_vuMeterRight1 = 0;
		_vuMeterRight2 = 0;
		_vuMeterLeft1 = 0;
		_vuMeterLeft2 = 0;
	} else {
		playNote(noteNum);
		_vuMeterRight1 = 10;
		_vuMeterRight2 = 10;
		_vuMeterLeft1 = 10;
		_vuMeterLeft2 = 10;
		if (_btn3KindToggle) {
			_objects[23].kind = 1;
			_objects[23].frameIndex = noteNum;
		} else {
			_objects[10].kind = 1;
			_objects[10].frameIndex = kNoteFrameTbl[noteNum].frameIndex;
			if (kNoteFrameTbl[noteNum].flag) {
				_objects[21].kind = 1;
				_objects[21].frameIndex = 7;
			}
		}
	}

	_currNoteNum = noteNum;

	if (_playerMode == 2 || _playerMode == 3) {
		_ticksDelta = _vm->_system->getMillis() - _noteStartTime;
		_track[_trackCount].ticks = _ticksDelta;
		if (_trackCount < kMaxTracks - 1)
			++_trackCount;
		_track[_trackCount].noteNum = noteNum;
	}

	_noteStartTime = _vm->_system->getMillis();

	if (noteNum != -2) {
		_resetAnims = false;
		if (_currPatchNum == 0) {
			_objects[25].kind = 1;
			_objects[28].kind = 0;
			_objects[25].frameIndex = 0;
			_objects[25].ticks = getAnimation(25)->frameTicks[0];
			_objects[26].frameIndex = 0;
			_objects[26].ticks = getAnimation(26)->frameTicks[0];
			_objects[27].frameIndex = 0;
			_objects[27].ticks = getAnimation(27)->frameTicks[0];
			_objects[30].kind = 1;
			_objects[33].kind = 0;
			_objects[30].frameIndex = 0;
			_objects[30].ticks = getAnimation(30)->frameTicks[0];
			_objects[31].frameIndex = 0;
			_objects[31].ticks = getAnimation(31)->frameTicks[0];
			_objects[32].frameIndex = 0;
			_objects[32].ticks = getAnimation(32)->frameTicks[0];
		} else if (_currPatchNum == 1) {
			_objects[29].kind = 1;
			_objects[33].kind = 0;
			_objects[29].frameIndex = 0;
			_objects[29].ticks = getAnimation(29)->frameTicks[0];
			_objects[31].frameIndex = 0;
			_objects[31].ticks = getAnimation(31)->frameTicks[0];
			_objects[32].frameIndex = 0;
			_objects[32].ticks = getAnimation(32)->frameTicks[0];
		} else if (_currPatchNum == 2) {
			_objects[24].kind = 1;
			_objects[28].kind = 0;
			_objects[24].frameIndex = 0;
			_objects[24].ticks = getAnimation(24)->frameTicks[0];
			_objects[26].frameIndex = 0;
			_objects[26].ticks = getAnimation(26)->frameTicks[0];
			_objects[27].frameIndex = 0;
			_objects[27].ticks = getAnimation(27)->frameTicks[0];
		}
	}

}

void MinigameBbAirGuitar::noteOff(int noteNum) {

	if (_currNoteNum != noteNum)
		return;

	if (noteNum != -2)
		stopNote(noteNum);

	_objects[21].kind = 0;
	_objects[23].kind = _objects[21].kind;
	_objects[10].kind = _objects[23].kind;

	_vuMeterRight1 = 0;
	_vuMeterRight2 = 0;
	_vuMeterLeft1 = 0;
	_vuMeterLeft2 = 0;

	_currNoteNum = -2;

	_objects[11].kind = 0;

	_ticksDelta = _vm->_system->getMillis() - _noteStartTime;

	if (_playerMode == 2 || _playerMode == 3) {
		if (_actionTrackPos + _ticksDelta > 15000)
			_ticksDelta = 15000 - _actionTrackPos;
		_track[_trackCount].ticks = _ticksDelta;
		if (_trackCount + 1 < 2048)
			++_trackCount;
		_track[_trackCount].noteNum = -2;
		_noteStartTime = _vm->_system->getMillis();
	}

	if (noteNum != -2) {
		if (_playerMode == 0) {
			_resetAnims = true;
			_noteStartTime = _vm->_system->getMillis();
		}
		if (_currPatchNum == 0) {
			_objects[25].frameIndex = 3;
			_objects[25].ticks = -1;
			_objects[26].frameIndex = 3;
			_objects[26].ticks = -1;
			_objects[27].frameIndex = 3;
			_objects[27].ticks = -1;
			_objects[30].frameIndex = 3;
			_objects[30].ticks = -1;
			_objects[31].frameIndex = 3;
			_objects[31].ticks = -1;
			_objects[32].frameIndex = 3;
			_objects[32].ticks = -1;
		} else if (_currPatchNum == 1) {
			_objects[29].frameIndex = 3;
			_objects[29].ticks = -1;
			_objects[31].frameIndex = 3;
			_objects[31].ticks = -1;
			_objects[32].frameIndex = 3;
			_objects[32].ticks = -1;
		} else if (_currPatchNum == 2) {
			_objects[24].frameIndex = 2;
			_objects[24].ticks = -1;
			_objects[26].frameIndex = 3;
			_objects[26].ticks = -1;
			_objects[27].frameIndex = 3;
			_objects[27].ticks = -1;
		}
	}

}

void MinigameBbAirGuitar::resetObjs() {
	_resetAnims = false;
	_objects[25].kind = 0;
	_objects[24].kind = 0;
	_objects[28].kind = 1;
	_objects[26].frameIndex = 0;
	_objects[26].ticks = -1;
	_objects[27].frameIndex = 0;
	_objects[27].ticks = -1;
	_objects[30].kind = 0;
	_objects[29].kind = 0;
	_objects[33].kind = 1;
	_objects[31].frameIndex = 0;
	_objects[31].ticks = -1;
	_objects[32].frameIndex = 0;
	_objects[32].ticks = -1;
}

void MinigameBbAirGuitar::loadSounds() {
	_vm->_sound->loadSound("bbairg/audio/rocktune.aif");
	for (uint i = 0; i < kPatchDirectoriesCount; ++i) {
		const char *patchDirectory = kPatchDirectories[i];
		for (uint j = 0; j < kNoteSoundFilenamesCount; ++j) {
			Common::String filename = Common::String::format("bbairg/audio/%s/%s", patchDirectory, kNoteSoundFilenames[j]);
			_vm->_sound->loadSound(filename.c_str());
		}
	}
}

void MinigameBbAirGuitar::playNote(int noteNum) {
	if (noteNum >= 0 && _currPatchNum >= 0)
		playSound(2 + _currPatchNum * kNoteSoundFilenamesCount + noteNum);
}

void MinigameBbAirGuitar::stopNote(int noteNum) {
	if (noteNum >= 0 && _currPatchNum >= 0)
		stopSound(2 + _currPatchNum * kNoteSoundFilenamesCount + noteNum);
}

bool MinigameBbAirGuitar::getLoadFilename(Common::String &filename) {
	GUI::FileBrowserDialog browser(0, "air", GUI::kFBModeLoad);

	if (browser.runModal() > 0) {
		filename = browser.getResult();
		return true;
	}

	return false;
}

bool MinigameBbAirGuitar::getSaveFilename(Common::String &filename) {
	GUI::FileBrowserDialog browser(0, "air", GUI::kFBModeSave);

	if (browser.runModal() > 0) {
		filename = browser.getResult();
		return true;
	}

	return false;
}

bool MinigameBbAirGuitar::querySaveModifiedDialog() {
	/* NOTE The original button captions don't fit so shortened variants are used
		Original ok button caption: "Yeah, heh, heh, save it!"
		Original discard button caption: "Who cares?  It sucked!"
	*/
	GUI::MessageDialog query(_("Hey Beavis - you didn't save that last Jam!"),
		_("Save it!"),
		_("It sucked!"));
	return query.runModal() == GUI::kMessageOK;
}

bool MinigameBbAirGuitar::querySaveModifiedTracks() {
	if (_modified && querySaveModifiedDialog()) {
		if (!saveTracks())
			return false;
	}
	return true;
}

bool MinigameBbAirGuitar::loadTracks() {
	if (_playerMode != 0)
		return false;

	if (!querySaveModifiedTracks())
		return false;

	Common::String filename;
	if (!getLoadFilename(filename))
		return false;

	Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
	Common::InSaveFile *stream = saveFileMan->openForLoading(filename);
	if (!loadFromStream(stream)) {
		Common::String msg = Common::String::format("%s is not a valid Air Guitar file", filename.c_str());
		GUI::MessageDialog dialog(msg);
		dialog.runModal();
	}
	delete stream;

	return true;
}

bool MinigameBbAirGuitar::saveTracks() {
	if (_playerMode != 0)
		return false;

	Common::String filename;
	if (!getSaveFilename(filename))
		return false;

	Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
	Common::OutSaveFile *stream = saveFileMan->openForSaving(filename);
	saveToStream(stream);
	delete stream;
	_modified = false;

	return true;
}

bool MinigameBbAirGuitar::loadFromStream(Common::ReadStream *stream) {
	uint32 magic = stream->readUint32BE();
	if (magic != MKTAG('A', 'I', 'R', 'G'))
		return false;
	for (uint i = 0; i < kMaxTracks; ++i) {
		_track[i].noteNum = stream->readByte();
		_track[i].ticks = stream->readUint16LE();
	}
	_trackCount = 0;
	_actionTrackPos = 0;
	while (_track[_trackCount].noteNum != -1) {
		_actionTrackPos += _track[_trackCount].ticks;
		++_trackCount;
	}
	_totalTrackLength = _actionTrackPos;
	_trackIndex = 0;
	_currTrackPos = 0;
	return true;
}

void MinigameBbAirGuitar::saveToStream(Common::WriteStream *stream) {
	stream->writeUint32BE(MKTAG('A', 'I', 'R', 'G'));
	for (uint i = 0; i < kMaxTracks; ++i) {
		stream->writeByte(_track[i].noteNum);
		stream->writeUint16LE(_track[i].ticks);
	}
}

} // End of namespace Bbvs