/* 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 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "dgds/clock.h"
#include "dgds/globals.h"
#include "dgds/dgds.h"
#include "dgds/font.h"
#include "dgds/includes.h"
#include "dgds/request.h"

namespace Dgds {

class DragonTimeGlobal : public ReadWriteGlobal<int16> {
public:
	DragonTimeGlobal(uint16 num, int16 *val, Clock &clock) : ReadWriteGlobal<int16>(num, val), _clock(clock) {}
	int16 set(int16 val) override {
		if (val != ReadWriteGlobal::get()) {
			ReadWriteGlobal::set(val);
			// notify that the time changed
			_clock.addGameTime(0);
		}
		return val;
	}

private:
	Clock &_clock;
};

Clock::Clock() : _visibleUser(true), _visibleScript(false), _days(0), _days2(0),
	_hours(0), _mins(0), _gameMinsAdded(0), _gameTicksUp(0), _gameTicksDown(0),
	_lastPlayTime(0), _millis(0)
{
}

Global *Clock::getDaysGlobal(uint16 num) {
	return new DragonTimeGlobal(num, &_days, *this);
}

Global *Clock::getDays2Global(uint16 num) {
	return new ReadWriteGlobal<int16>(num, &_days2);
}

Global *Clock::getHoursGlobal(uint16 num) {
	return new DragonTimeGlobal(num, &_hours, *this);
}

Global *Clock::getMinsGlobal(uint16 num) {
	return new DragonTimeGlobal(num, &_mins, *this);
}

Global *Clock::getGameMinsAddedGlobal(uint16 num) {
	return new ReadOnlyGlobal<int16>(num, &_gameMinsAdded);
}

Global *Clock::getGameTicksUpGlobal(uint16 num) {
	return new ReadOnlyGlobal<int16>(num, &_gameTicksUp);
}

Global *Clock::getGameTicksDownGlobal(uint16 num) {
	return new ReadOnlyGlobal<int16>(num, &_gameTicksDown);
}

static int16 DAYS_PER_MONTH[] = {
	31, 28, 31, 30,
	31, 30, 31, 31,
	30, 31, 30, 31
};

void Clock::addGameTime(int mins) {
	_gameMinsAdded += mins;
	int nhours = (_mins + mins) / 60;
	_mins = (_mins + mins) % 60;
	if (_mins < 0) {
		_mins += 0x3c;
		nhours--;
	}
	int ndays = (_hours + nhours) / 24;
	_hours = (_hours + nhours) % 24;
	if (_hours < 0) {
		_hours += 24;
		_days -= 1;
	}
	_days += ndays;
	// TODO: if any change was made to days/hours/mins..
	//if (plusmins + nhours + ndays != 0)
	//  UINT_39e5_0ffa = 0;
}

Common::String Clock::getTimeStr() const {
	int month = 0;
	int day = _days + _days2 + 1;
	while (day > DAYS_PER_MONTH[month]) {
		day -= DAYS_PER_MONTH[month];
		month++;
		if (month == ARRAYSIZE(DAYS_PER_MONTH))
			month = 0;
	}

	DgdsEngine *engine = DgdsEngine::getInstance();

	if (engine->getGameId() == GID_WILLY) {
		return Common::String::format("DAY %d, %2d:%02d", day - 1, _hours, _mins);
	} else if (engine->getGameLang() == Common::EN_ANY) {
		return Common::String::format("%2d/%02d %2d:%02d", month + 1, day, _hours, _mins);
	} else if (engine->getGameLang() == Common::DE_DEU) {
		return Common::String::format("%2d.%d %2d.%02d", day, month + 1, _hours, _mins);
	} else {
		error("Unsupported language %d", DgdsEngine::getInstance()->getGameLang());
	}
}

void Clock::draw(Graphics::ManagedSurface &surf) {
	DgdsEngine *engine = DgdsEngine::getInstance();
	if (!_visibleUser || !_visibleScript || engine->getGameId() != GID_DRAGON)
		return;

	const Common::String clockStr = getTimeStr();

	const FontManager *fontman = engine->getFontMan();
	const DgdsFont *font = fontman->getFont(FontManager::k4x5Font);
	int width = font->getMaxCharWidth() * 12 + 3;
	_drawPos.top = 0;
	_drawPos.bottom = font->getFontHeight() + 6;
	_drawPos.right = SCREEN_WIDTH;
	_drawPos.left = SCREEN_WIDTH - width;

	RequestData::fillBackground(&surf, _drawPos.left + 2, _drawPos.top + 2, _drawPos.width() - 4, _drawPos.height() - 4, 65);
	font->drawString(&surf, clockStr, _drawPos.left + 3, _drawPos.top + 3, _drawPos.width(), 0);
}

// Confirmed by timing the clock tick up in the inventory of Dragon and Willy Beamish
// - 5 game minutes are exactly 25 real time seconds.
static const int MILLIS_PER_GAME_MIN = 5000;
static const int MILLIS_PER_TIMER_TICK = 60; // ~16.667 ticks per second.

void Clock::update(bool gameRunning) {
	uint32 playTimeNow = DgdsEngine::getInstance()->getThisFrameMs();
	// These timers are updated whether or not the game is running
	_gameTicksUp = playTimeNow / MILLIS_PER_TIMER_TICK;

	// Is there any reason to make this variable anything other than negative the other one??
	// There seems to be no other way to set them as they are RO globals.
	_gameTicksDown = -_gameTicksUp;

	uint32 lastLastPlayTime = _lastPlayTime;
	_lastPlayTime = playTimeNow;

	if (lastLastPlayTime == 0 || !gameRunning)
		return;

	_millis += playTimeNow - lastLastPlayTime;
	int16 mins_to_add = _millis / MILLIS_PER_GAME_MIN;
	_millis = _millis % MILLIS_PER_GAME_MIN;

	if (mins_to_add)
		addGameTime(mins_to_add);
}

Common::String Clock::dump() const {
	return Common::String::format("days %d hours %d mins %d", _days, _mins, _hours);
}

Common::Error Clock::syncState(Common::Serializer &s) {
	s.syncAsUint32LE(_lastPlayTime);
	s.syncAsUint32LE(_millis);

	s.syncAsSint16LE(_gameMinsAdded);
	s.syncAsSint16LE(_gameTicksUp);
	s.syncAsSint16LE(_gameTicksDown);
	s.syncAsSint16LE(_days);
	s.syncAsSint16LE(_days2);
	s.syncAsSint16LE(_hours);
	s.syncAsSint16LE(_mins);

	s.syncAsSint16LE(_drawPos.left);
	s.syncAsSint16LE(_drawPos.top);
	s.syncAsSint16LE(_drawPos.right);
	s.syncAsSint16LE(_drawPos.bottom);

	s.syncAsByte(_visibleScript);
	s.syncAsByte(_visibleUser);

	return Common::kNoError;
}

} // end namespace Dgds
