/* 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
 * 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 "ags/lib/allegro.h"
#include "ags/plugins/ags_snow_rain/weather.h"
#include "ags/plugins/ags_snow_rain/ags_snow_rain.h"
#include "ags/ags.h"

namespace AGS3 {
namespace Plugins {
namespace AGSSnowRain {

#define signum(x) ((x > 0) ? 1 : -1)

const unsigned int Magic = 0xCAFE0000;
const unsigned int Version = 2;
const unsigned int SaveMagic = Magic + Version;


void View::syncGame(Serializer &s) {
	s.syncAsInt(view);
	s.syncAsInt(loop);
	s.syncAsBool(is_default);

	// Pointer saved/loaded raw structure, which included bitmap pointer
	int dummy = 0;
	s.syncAsInt(dummy);
}

/*------------------------------------------------------------------*/

Weather::Weather(bool IsSnow, int32 &scrWidth, int32 &scrHeight, IAGSEngine *&engine) :
		_mIsSnow(IsSnow), _screenWidth(scrWidth), _screenHeight(scrHeight), _engine(engine) {
	Initialize();
}

void Weather::Update() {
	if (_mTargetAmount > _mAmount)
		_mAmount++;
	else if (_mTargetAmount < _mAmount)
		_mAmount--;

	if (!ReinitializeViews())
		return;

	int i;
	for (i = 0; i < _mAmount * 2; i++) {
		_mParticles[i].y += _mParticles[i].speed;
		_mParticles[i].x += _mWindSpeed;

		if (_mParticles[i].x < 0)
			_mParticles[i].x += _screenWidth;

		if (_mParticles[i].x > _screenWidth - 1)
			_mParticles[i].x -= _screenWidth;

		if (_mParticles[i].y > _mParticles[i].max_y) {
			_mParticles[i].y = -1 * (::AGS::g_vm->getRandomNumber(0x7fffffff) % _screenHeight);
			_mParticles[i].x = ::AGS::g_vm->getRandomNumber(0x7fffffff) % _screenWidth;
			_mParticles[i].alpha = ::AGS::g_vm->getRandomNumber(0x7fffffff) % _mDeltaAlpha + _mMinAlpha;
			_mParticles[i].speed = (float)(::AGS::g_vm->getRandomNumber(0x7fffffff) % _mDeltaFallSpeed + _mMinFallSpeed) / 50.0f;
			_mParticles[i].max_y = ::AGS::g_vm->getRandomNumber(0x7fffffff) % _mDeltaBaseline + _mTopBaseline;
		} else if ((_mParticles[i].y > 0) && (_mParticles[i].alpha > 0))
			_engine->BlitSpriteTranslucent(_mParticles[i].x, _mParticles[i].y, _mViews[_mParticles[i].kind_id].bitmap, _mParticles[i].alpha);
	}

	_engine->MarkRegionDirty(0, 0, _screenWidth, _screenHeight);
}

void Weather::UpdateWithDrift() {
	if (_mTargetAmount > _mAmount)
		_mAmount++;
	else if (_mTargetAmount < _mAmount)
		_mAmount--;

	if (!ReinitializeViews())
		return;

	int i, drift;
	for (i = 0; i < _mAmount * 2; i++) {
		_mParticles[i].y += _mParticles[i].speed;
		drift = _mParticles[i].drift * sin((float)(_mParticles[i].y +
		                                   _mParticles[i].drift_offset) * _mParticles[i].drift_speed * 2.0f * M_PI / 360.0f);

		if (signum(_mWindSpeed) == signum(drift))
			_mParticles[i].x += _mWindSpeed;
		else
			_mParticles[i].x += _mWindSpeed / 4;

		if (_mParticles[i].x < 0)
			_mParticles[i].x += _screenWidth;

		if (_mParticles[i].x > _screenWidth - 1)
			_mParticles[i].x -= _screenWidth;

		if (_mParticles[i].y > _mParticles[i].max_y) {
			_mParticles[i].y = -1 * (::AGS::g_vm->getRandomNumber(0x7fffffff) % _screenHeight);
			_mParticles[i].x = ::AGS::g_vm->getRandomNumber(0x7fffffff) % _screenWidth;
			_mParticles[i].alpha = ::AGS::g_vm->getRandomNumber(0x7fffffff) % _mDeltaAlpha + _mMinAlpha;
			_mParticles[i].speed = (float)(::AGS::g_vm->getRandomNumber(0x7fffffff) % _mDeltaFallSpeed + _mMinFallSpeed) / 50.0f;
			_mParticles[i].max_y = ::AGS::g_vm->getRandomNumber(0x7fffffff) % _mDeltaBaseline + _mTopBaseline;
			_mParticles[i].drift = ::AGS::g_vm->getRandomNumber(0x7fffffff) % _mDeltaDrift + _mMinDrift;
			_mParticles[i].drift_speed = (::AGS::g_vm->getRandomNumber(0x7fffffff) % _mDeltaDriftSpeed + _mMinDriftSpeed) / 50.0f;
		} else if ((_mParticles[i].y > 0) && (_mParticles[i].alpha > 0))
			_engine->BlitSpriteTranslucent(_mParticles[i].x + drift, _mParticles[i].y, _mViews[_mParticles[i].kind_id].bitmap, _mParticles[i].alpha);
	}

	_engine->MarkRegionDirty(0, 0, _screenWidth, _screenHeight);
}

void Weather::syncGame(Serializer &s) {
	int saveVersion = SaveMagic;
	s.syncAsInt(saveVersion);

	if (s.isLoading() && (uint)saveVersion != SaveMagic) {
		_engine->AbortGame("ags_snowrain: bad save.");
		return;
	}

	// TODO: At some point check whether the original did a packed
	// structure for Weather, or if bools were padded to 4 bytes
	s.syncAsBool(_mIsSnow);
	s.syncAsInt(_mMinDrift);
	s.syncAsInt(_mMaxDrift);
	s.syncAsInt(_mDeltaDrift);
	s.syncAsInt(_mMinDriftSpeed);
	s.syncAsInt(_mMaxDriftSpeed);
	s.syncAsInt(_mDeltaDriftSpeed);
	s.syncAsInt(_mAmount);
	s.syncAsInt(_mTargetAmount);
	s.syncAsInt(_mMinAlpha);
	s.syncAsInt(_mMaxAlpha);
	s.syncAsInt(_mDeltaAlpha);
	s.syncAsFloat(_mWindSpeed);
	s.syncAsInt(_mTopBaseline);
	s.syncAsInt(_mBottomBaseline);
	s.syncAsInt(_mDeltaBaseline);
	s.syncAsInt(_mMinFallSpeed);
	s.syncAsInt(_mMaxFallSpeed);
	s.syncAsInt(_mDeltaFallSpeed);

	for (int i = 0; i < 5; ++i)
		_mViews[i].syncGame(s);

	if (s.isLoading())
		InitializeParticles();
}

bool Weather::ReinitializeViews() {
	if ((_mViews[4].view == -1) || (_mViews[4].loop == -1))
		return false;

	AGSViewFrame *view_frame = _engine->GetViewFrame(_mViews[4].view, _mViews[4].loop, 0);
	BITMAP *default_bitmap = _engine->GetSpriteGraphic(view_frame->pic);

	int i;
	for (i = 0; i < 5; i++) {
		if (_mViews[i].bitmap != nullptr) {
			if (_mViews[i].is_default)
				_mViews[i].bitmap = default_bitmap;
			else {
				view_frame = _engine->GetViewFrame(_mViews[i].view, _mViews[i].loop, 0);
				_mViews[i].bitmap = _engine->GetSpriteGraphic(view_frame->pic);
			}
		}
	}

	return true;
}

bool Weather::IsActive() {
	return (_mAmount > 0) || (_mTargetAmount != _mAmount);
}

void Weather::EnterRoom() {
	_mAmount = _mTargetAmount;

	// If baseline is not fixed, reset and clamp to the new screenHeight
	if (!_mBaselineFixed)
		ResetBaseline();
}

void Weather::ClipToRange(int &variable, int min, int max) {
	if (variable < min)
		variable = min;

	if (variable > max)
		variable = max;
}

void Weather::Initialize() {
	SetDriftRange(10, 100);
	SetDriftSpeed(10, 120);

	SetTransparency(0, 0);
	SetWindSpeed(0);
	ResetBaseline();

	if (_mIsSnow)
		SetFallSpeed(10, 70);
	else
		SetFallSpeed(100, 300);

	_mViewsInitialized = false;

	int i;
	for (i = 0; i < 5; i++) {
		_mViews[i].is_default = true;
		_mViews[i].view = -1;
		_mViews[i].loop = -1;
		_mViews[i].bitmap = nullptr;
	}

	SetAmount(0);
}

void Weather::InitializeParticles() {
	for (uint i = 0; i < ARRAYSIZE(_mParticles); i++) {
		_mParticles[i].clear();
		_mParticles[i].kind_id = ::AGS::g_vm->getRandomNumber(0x7fffffff) % 5;
		_mParticles[i].y = ::AGS::g_vm->getRandomNumber(0x7fffffff) % (_screenHeight * 2) - _screenHeight;
		_mParticles[i].x = ::AGS::g_vm->getRandomNumber(0x7fffffff) % _screenWidth;
		_mParticles[i].alpha = ::AGS::g_vm->getRandomNumber(0x7fffffff) % _mDeltaAlpha + _mMinAlpha;
		_mParticles[i].speed = (float)(::AGS::g_vm->getRandomNumber(0x7fffffff) % _mDeltaFallSpeed + _mMinFallSpeed) / 50.0f;
		_mParticles[i].max_y = ::AGS::g_vm->getRandomNumber(0x7fffffff) % _mDeltaBaseline + _mTopBaseline;
		_mParticles[i].drift = ::AGS::g_vm->getRandomNumber(0x7fffffff) % _mDeltaDrift + _mMinDrift;
		_mParticles[i].drift_speed = (::AGS::g_vm->getRandomNumber(0x7fffffff) % _mDeltaDriftSpeed + _mMinDriftSpeed) / 50.0f;
		_mParticles[i].drift_offset = ::AGS::g_vm->getRandomNumber(0x7fffffff) % 100;
	}
}

void Weather::SetDriftRange(int min_value, int max_value) {
	ClipToRange(min_value, 0, 100);
	ClipToRange(max_value, 0, 100);

	if (min_value > max_value)
		min_value = max_value;

	_mMinDrift = min_value / 2;
	_mMaxDrift = max_value / 2;
	_mDeltaDrift = _mMaxDrift - _mMinDrift;

	if (_mDeltaDrift == 0)
		_mDeltaDrift = 1;
}

void Weather::SetDriftSpeed(int min_value, int max_value) {
	ClipToRange(min_value, 0, 200);
	ClipToRange(max_value, 0, 200);

	if (min_value > max_value)
		min_value = max_value;

	_mMinDriftSpeed = min_value;
	_mMaxDriftSpeed = max_value;
	_mDeltaDriftSpeed = _mMaxDriftSpeed - _mMinDriftSpeed;

	if (_mDeltaDriftSpeed == 0)
		_mDeltaDriftSpeed = 1;
}

void Weather::ChangeAmount(int amount) {
	ClipToRange(amount, 0, 1000);

	_mTargetAmount = amount;
}

void Weather::SetView(int kind_id, int event, int view, int loop) {
	AGSViewFrame *view_frame = _engine->GetViewFrame(view, loop, 0);
	_mViews[kind_id].bitmap = _engine->GetSpriteGraphic(view_frame->pic);
	_mViews[kind_id].is_default = false;
	_mViews[kind_id].view = view;
	_mViews[kind_id].loop = loop;

	if (!_mViewsInitialized)
		SetDefaultView(view, loop);
}

void Weather::SetDefaultView(int view, int loop) {
	AGSViewFrame *view_frame = _engine->GetViewFrame(view, loop, 0);
	BITMAP *bitmap = _engine->GetSpriteGraphic(view_frame->pic);

	_mViewsInitialized = true;

	int i;
	for (i = 0; i < 5; i++) {
		if (_mViews[i].is_default) {
			_mViews[i].view = view;
			_mViews[i].loop = loop;
			_mViews[i].bitmap = bitmap;
		}
	}
}

void Weather::SetTransparency(int min_value, int max_value) {
	ClipToRange(min_value, 0, 100);
	ClipToRange(max_value, 0, 100);

	if (min_value > max_value)
		min_value = max_value;

	_mMinAlpha = 255 - floor((float)max_value * 2.55f + 0.5f);
	_mMaxAlpha = 255 - floor((float)min_value * 2.55f + 0.5f);
	_mDeltaAlpha = _mMaxAlpha - _mMinAlpha;

	if (_mDeltaAlpha == 0)
		_mDeltaAlpha = 1;

	int i;
	for (i = 0; i < 2000; i++)
		_mParticles[i].alpha = ::AGS::g_vm->getRandomNumber(0x7fffffff) % _mDeltaAlpha + _mMinAlpha;
}

void Weather::SetWindSpeed(int value) {
	ClipToRange(value, -200, 200);

	_mWindSpeed = (float)value / 20.0f;
}

void Weather::SetBaseline(int top, int bottom) {
	if (_screenHeight > 0) {
		ClipToRange(top, 0, _screenHeight);
		ClipToRange(bottom, 0, _screenHeight);
	}

	if (top > bottom)
		top = bottom;

	_mTopBaseline = top;
	_mBottomBaseline = bottom;
	_mDeltaBaseline = _mBottomBaseline - _mTopBaseline;

	if (_mDeltaBaseline == 0)
		_mDeltaBaseline = 1;

	_mBaselineFixed = true;
}

void Weather::ResetBaseline() {
	_mTopBaseline = 0;
	_mBottomBaseline = _screenHeight;
	_mDeltaBaseline = _screenHeight;
	_mBaselineFixed = false;
}

void Weather::SetAmount(int amount) {
	ClipToRange(amount, 0, 1000);

	_mAmount = _mTargetAmount = amount;

	InitializeParticles();
}

void Weather::SetFallSpeed(int min_value, int max_value) {
	ClipToRange(min_value, 0, 1000);
	ClipToRange(max_value, 0, 1000);

	if (min_value > max_value)
		min_value = max_value;

	_mMinFallSpeed = min_value;
	_mMaxFallSpeed = max_value;
	_mDeltaFallSpeed = _mMaxFallSpeed - _mMinFallSpeed;

	if (_mDeltaFallSpeed == 0)
		_mDeltaFallSpeed = 1;
}

} // namespace AGSSnowRain
} // namespace Plugins
} // namespace AGS3
