/* 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 "neverhood/modules/module3000.h"
#include "neverhood/modules/module3000_sprites.h"

namespace Neverhood {

// Scene3009

enum {
	kCTSNull				= 0,
	kCTSBreakWall			= 1,
	kCTSWall				= 2,
	kCTSEmptyness			= 3,
	kCTSFireRobotNoTarget	= 4,
	kCTSFireRobotIsTarget	= 5,
	kCTSFireNoRobot			= 6,
	kCTSRaiseCannon			= 7,
	kCTSRightRobotNoTarget	= 8,
	kCTSRightRobotIsTarget	= 9,
	kCTSRightNoRobot		= 10,
	kCTSLeftRobotNoTarget	= 11,
	kCTSLeftRobotIsTarget	= 12,
	kCTSLeftNoRobot			= 13,
	kCTSLowerCannon			= 14,
	kCTSCount				= 14
};

static const uint32 kSsScene3009SymbolEdgesFileHashes[] = {
	0x618827A0,
	0xB1A92322
};

static const uint32 kSsScene3009TargetLineFileHashes[] = {
	0x4011018C,
	0x15086623
};

static const NPoint kAsScene3009SymbolPoints[] = {
	{289, 338},
	{285, 375},
	{284, 419},
	{456, 372},
	{498, 372},
	{541, 372}
};

static const uint32 kAsScene3009SymbolFileHashes[] = {
	0x24542582,
	0x1CD61D96
};

static const uint32 kSsScene3009SymbolArrowFileHashes1[] = {
	0x24016060,
	0x21216221,
	0x486160A0,
	0x42216422,
	0x90A16120,
	0x84216824,
	0x08017029,
	0x08217029,
	0x10014032,
	0x10214032,
	0x20012004,
	0x20212004
};

static const uint32 kSsScene3009SymbolArrowFileHashes2[] = {
	0x40092024,
	0x01636002,
	0x8071E028,
	0x02A56064,
	0x00806031,
	0x052960A8,
	0x0A116130,
	0x0A316130,
	0x14216200,
	0x14016200,
	0x28416460,
	0x28616460
};

SsScene3009FireCannonButton::SsScene3009FireCannonButton(NeverhoodEngine *vm, Scene3009 *parentScene)
	: StaticSprite(vm, 1400), _parentScene(parentScene), _isClicked(false) {

	loadSprite(0x120B24B0, kSLFDefDrawOffset | kSLFDefPosition | kSLFDefCollisionBoundsOffset, 400);
	setVisible(false);
	SetUpdateHandler(&SsScene3009FireCannonButton::update);
	SetMessageHandler(&SsScene3009FireCannonButton::handleMessage);
	loadSound(0, 0x3901B44F);
}

void SsScene3009FireCannonButton::update() {
	updatePosition();
	if (_isClicked && !isSoundPlaying(0)) {
		sendMessage(_parentScene, 0x2000, 0);
		setVisible(false);
	}
}

uint32 SsScene3009FireCannonButton::handleMessage(int messageNum, const MessageParam &param, Entity *sender) {
	uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
	switch (messageNum) {
	case 0x1011:
		if (!_isClicked && !_parentScene->isTurning()) {
			_isClicked = true;
			setVisible(true);
			playSound(0);
		}
		messageResult = 1;
		break;
	default:
		break;
	}
	return messageResult;
}

SsScene3009SymbolEdges::SsScene3009SymbolEdges(NeverhoodEngine *vm, int index)
	: StaticSprite(vm, 1400), _blinkCountdown(0) {

	loadSprite(kSsScene3009SymbolEdgesFileHashes[index], kSLFDefDrawOffset | kSLFDefPosition, 600);
	if (getGlobalVar(V_ROBOT_HIT))
		hide();
	else
		startBlinking();
	SetUpdateHandler(&SsScene3009SymbolEdges::update);
}

void SsScene3009SymbolEdges::update() {
	if (_blinkCountdown != 0 && (--_blinkCountdown == 0)) {
		if (_blinkToggle) {
			setVisible(true);
		} else {
			setVisible(false);
		}
		updatePosition();
		_blinkCountdown = 3;
		_blinkToggle = !_blinkToggle;
	}
}

void SsScene3009SymbolEdges::show() {
	setVisible(true);
	updatePosition();
	_blinkCountdown = 0;
}

void SsScene3009SymbolEdges::hide() {
	setVisible(false);
	updatePosition();
	_blinkCountdown = 0;
}

void SsScene3009SymbolEdges::startBlinking() {
	setVisible(true);
	updatePosition();
	_blinkCountdown = 3;
	_blinkToggle = true;
}

SsScene3009TargetLine::SsScene3009TargetLine(NeverhoodEngine *vm, int index)
	: StaticSprite(vm, 1400) {

	loadSprite(kSsScene3009TargetLineFileHashes[index], kSLFDefDrawOffset | kSLFDefPosition, 600);
	setVisible(false);
}

void SsScene3009TargetLine::show() {
	setVisible(true);
	updatePosition();
}

SsScene3009SymbolArrow::SsScene3009SymbolArrow(NeverhoodEngine *vm, Sprite *asSymbol, int index)
	: StaticSprite(vm, 1400), _asSymbol(asSymbol), _index(index), _enabled(true), _countdown(0) {

	_incrDecr = _index % 2;

	createSurface(1200, 33, 31);
	loadSprite(kSsScene3009SymbolArrowFileHashes2[_index], kSLFDefPosition);
	_drawOffset.set(0, 0, 33, 31);
	_collisionBoundsOffset = _drawOffset;
	updateBounds();
	_needRefresh = true;

	SetUpdateHandler(&SsScene3009SymbolArrow::update);
	SetMessageHandler(&SsScene3009SymbolArrow::handleMessage);
	loadSound(0, 0x2C852206);
}

void SsScene3009SymbolArrow::hide() {
	_enabled = false;
	setVisible(false);
}

void SsScene3009SymbolArrow::update() {
	updatePosition();
	if (_countdown != 0 && (--_countdown == 0)) {
		loadSprite(kSsScene3009SymbolArrowFileHashes2[_index], kSLFDefDrawOffset);
	}
}

uint32 SsScene3009SymbolArrow::handleMessage(int messageNum, const MessageParam &param, Entity *sender) {
	uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
	switch (messageNum) {
	case 0x1011:
		if (_enabled && _countdown == 0) {
			_countdown = 2;
			loadSprite(kSsScene3009SymbolArrowFileHashes1[_index], kSLFDefDrawOffset);
			playSound(0);
			sendMessage(_asSymbol, NM_KLAYMEN_CLIMB_LADDER, _incrDecr);
		}
		messageResult = 1;
		break;
	default:
		break;
	}
	return messageResult;
}

AsScene3009VerticalIndicator::AsScene3009VerticalIndicator(NeverhoodEngine *vm, Scene3009 *parentScene, int index)
	: AnimatedSprite(vm, 1000), _parentScene(parentScene), _enabled(false) {

	_x = 300;
	_y = getGlobalVar(V_CANNON_RAISED) ? 52 : 266;
	createSurface1(0xC2463913, 1200);
	_needRefresh = true;
	updatePosition();
	setVisible(false);
	SetUpdateHandler(&AnimatedSprite::update);
	SetMessageHandler(&AsScene3009VerticalIndicator::handleMessage);
}

void AsScene3009VerticalIndicator::show() {
	startAnimation(0xC2463913, 0, -1);
	setVisible(true);
	updatePosition();
	_enabled = true;
}

uint32 AsScene3009VerticalIndicator::handleMessage(int messageNum, const MessageParam &param, Entity *sender) {
	uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
	switch (messageNum) {
	case 0x1011:
		if (_enabled) {
			sendMessage(_parentScene, NM_POSITION_CHANGE, 0);
		}
		messageResult = 1;
		break;
	default:
		break;
	}
	return messageResult;
}

AsScene3009HorizontalIndicator::AsScene3009HorizontalIndicator(NeverhoodEngine *vm, Scene3009 *parentScene, uint32 cannonTargetStatus)
	: AnimatedSprite(vm, 1000), _parentScene(parentScene), _enabled(false) {

	_x = getGlobalVar(V_CANNON_TURNED) ? 533 : 92;
	_y = 150;
	createSurface1(0xC0C12954, 1200);
	_needRefresh = true;
	updatePosition();
	setVisible(false);
	SetUpdateHandler(&AnimatedSprite::update);
	SetMessageHandler(&AsScene3009HorizontalIndicator::handleMessage);
	if (cannonTargetStatus == kCTSRightRobotNoTarget || cannonTargetStatus == kCTSRightRobotIsTarget || cannonTargetStatus == kCTSRightNoRobot) {
		SetSpriteUpdate(&AsScene3009HorizontalIndicator::suMoveRight);
		_x = 280;
	}
}

uint32 AsScene3009HorizontalIndicator::handleMessage(int messageNum, const MessageParam &param, Entity *sender) {
	uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
	switch (messageNum) {
	case 0x1011:
		if (_enabled) {
			sendMessage(_parentScene, 0x2004, 0);
		}
		messageResult = 1;
		break;
	default:
		break;
	}
	return messageResult;
}

void AsScene3009HorizontalIndicator::suMoveLeft() {
	_x -= 6;
	if (_x < 92) {
		SetSpriteUpdate(nullptr);
		_x = 92;
	}
}

void AsScene3009HorizontalIndicator::suMoveRight() {
	_x += 6;
	if (_x > 533) {
		SetSpriteUpdate(nullptr);
		_x = 533;
	}
}

void AsScene3009HorizontalIndicator::show() {
	startAnimation(0xC0C12954, 0, -1);
	setVisible(true);
	updatePosition();
	_enabled = true;
}

void AsScene3009HorizontalIndicator::stMoveLeft() {
	_x = 533;
	SetSpriteUpdate(&AsScene3009HorizontalIndicator::suMoveLeft);
}

void AsScene3009HorizontalIndicator::stMoveRight() {
	_x = 330;
	SetSpriteUpdate(&AsScene3009HorizontalIndicator::suMoveRight);
}

AsScene3009Symbol::AsScene3009Symbol(NeverhoodEngine *vm, Scene3009 *parentScene, int symbolPosition)
	: AnimatedSprite(vm, 1100), _parentScene(parentScene), _symbolPosition(symbolPosition) {

	_symbolIndex = getSubVar(VA_CURR_CANNON_SYMBOLS, _symbolPosition);

	_x = kAsScene3009SymbolPoints[_symbolPosition].x;
	_y = kAsScene3009SymbolPoints[_symbolPosition].y;
	createSurface1(kAsScene3009SymbolFileHashes[_symbolPosition / 3], 1200);
	startAnimation(kAsScene3009SymbolFileHashes[_symbolPosition / 3], _symbolIndex, -1);
	_newStickFrameIndex = _symbolIndex;
	_needRefresh = true;
	updatePosition();
	SetUpdateHandler(&AnimatedSprite::update);
	SetMessageHandler(&AsScene3009Symbol::handleMessage);
	_ssArrowPrev = _parentScene->insertSprite<SsScene3009SymbolArrow>(this, _symbolPosition * 2 + 0);
	_parentScene->addCollisionSprite(_ssArrowPrev);
	_ssArrowNext = _parentScene->insertSprite<SsScene3009SymbolArrow>(this, _symbolPosition * 2 + 1);
	_parentScene->addCollisionSprite(_ssArrowNext);
}

uint32 AsScene3009Symbol::handleMessage(int messageNum, const MessageParam &param, Entity *sender) {
	uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
	switch (messageNum) {
	case NM_KLAYMEN_CLIMB_LADDER:
		if (param.asInteger()) {
			if (_symbolIndex == 11)
				_symbolIndex = 0;
			else
				_symbolIndex++;
		} else {
			if (_symbolIndex == 0)
				_symbolIndex = 11;
			else
				_symbolIndex--;
		}
		startAnimation(kAsScene3009SymbolFileHashes[_symbolPosition / 3], _symbolIndex, -1);
		_newStickFrameIndex = _symbolIndex;
		setSubVar(VA_CURR_CANNON_SYMBOLS, _symbolPosition, _symbolIndex);
		if (_symbolPosition / 3 == 0) {
			sendMessage(_parentScene, 0x2001, 0);
		} else {
			sendMessage(_parentScene, 0x2003, 0);
		}
		messageResult = 1;
		break;
	default:
		break;
	}
	return messageResult;
}

void AsScene3009Symbol::hide() {
	_ssArrowPrev->hide();
	_ssArrowNext->hide();
}

// Scene3010

static const uint32 kScene3010ButtonNameHashes[] = {
	0x304008D2,
	0x40119852,
	0x01180951
};

static const uint32 kScene3010DeadBoltButtonFileHashes1[] = {
	0x301024C2,
	0x20280580,
	0x30200452
};

static const uint32 kScene3010DeadBoltButtonFileHashes2[] = {
	0x50C025A8,
	0x1020A0A0,
	0x5000A7E8
};

static const NPoint kAsScene3010DeadBoltPoints[] = {
	{550, 307},
	{564, 415},
	{560, 514}
};

static const uint32 kAsScene3010DeadBoltFileHashes2[] = {
	0x181A0042,
	0x580A08F2,
	0x18420076
};

static const uint32 kAsScene3010DeadBoltFileHashes1[] = {
	0x300E105A,
	0x804E0052,
	0x040E485A
};

SsScene3010DeadBoltButton::SsScene3010DeadBoltButton(NeverhoodEngine *vm, Scene *parentScene, int buttonIndex, int initCountdown, bool initDisabled)
	: StaticSprite(vm, 900), _parentScene(parentScene), _buttonLocked(false), _countdown1(0), _countdown2(0), _buttonIndex(buttonIndex) {

	_buttonEnabled = getSubVar(VA_LOCKS_DISABLED, kScene3010ButtonNameHashes[_buttonIndex]) != 0;
	createSurface(400, 88, 95);
	setSprite(kScene3010DeadBoltButtonFileHashes2[_buttonIndex]);
	if (initDisabled)
		disableButton();
	else if (_buttonEnabled)
		_countdown1 = initCountdown * 12 + 1;
	loadSound(0, 0xF4217243);
	loadSound(1, 0x44049000);
	loadSound(2, 0x6408107E);
	SetUpdateHandler(&SsScene3010DeadBoltButton::update);
	SetMessageHandler(&SsScene3010DeadBoltButton::handleMessage);
}

void SsScene3010DeadBoltButton::update() {

	if (_countdown1 != 0 && (--_countdown1 == 0)) {
		playSound(0);
		setVisible(false);
		setSprite(kScene3010DeadBoltButtonFileHashes1[_buttonIndex]);
	}

	if (_countdown2 != 0 && (--_countdown2 == 0)) {
		setVisible(true);
		setSprite(kScene3010DeadBoltButtonFileHashes2[_buttonIndex]);
	}

}

uint32 SsScene3010DeadBoltButton::handleMessage(int messageNum, const MessageParam &param, Entity *sender) {
	uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
	switch (messageNum) {
	case 0x1011:
		if (!_buttonLocked && _countdown1 == 0) {
			if (_buttonEnabled) {
				playSound(1);
				playSound(2);
				setVisible(true);
				_buttonLocked = true;
				sendMessage(_parentScene, 0x2000, _buttonIndex);
			} else {
				sendMessage(_parentScene, NM_POSITION_CHANGE, _buttonIndex);
			}
			_needRefresh = true;
			updatePosition();
		}
		messageResult = 1;
		break;
	default:
		break;
	}
	return messageResult;
}

void SsScene3010DeadBoltButton::disableButton() {
	_buttonLocked = true;
	setSprite(kScene3010DeadBoltButtonFileHashes1[_buttonIndex]);
	setVisible(true);
}

void SsScene3010DeadBoltButton::setSprite(uint32 fileHash) {
	loadSprite(fileHash, kSLFDefDrawOffset | kSLFDefPosition | kSLFDefCollisionBoundsOffset);
}

void SsScene3010DeadBoltButton::setCountdown(int count) {
	_countdown2 = count * 18 + 1;
}

AsScene3010DeadBolt::AsScene3010DeadBolt(NeverhoodEngine *vm, Scene *parentScene, int boltIndex, bool initUnlocked)
	: AnimatedSprite(vm, 1100), _parentScene(parentScene), _boltIndex(boltIndex), _soundToggle(true),
	_unlocked(false), _locked(false), _countdown(0) {

	_x = kAsScene3010DeadBoltPoints[_boltIndex].x;
	_y = kAsScene3010DeadBoltPoints[_boltIndex].y;

	if (getSubVar(VA_LOCKS_DISABLED, kScene3010ButtonNameHashes[_boltIndex])) {
		createSurface1(kAsScene3010DeadBoltFileHashes1[_boltIndex], 1200);
		startAnimation(kAsScene3010DeadBoltFileHashes1[_boltIndex], 0, -1);
		loadSound(0, 0x46005BC4);
	} else {
		createSurface1(kAsScene3010DeadBoltFileHashes2[_boltIndex], 1200);
		startAnimation(kAsScene3010DeadBoltFileHashes2[_boltIndex], 0, -1);
		loadSound(0, 0x420073DC);
		loadSound(1, 0x420073DC);
	}

	setVisible(false);
	stIdle();
	if (initUnlocked)
		unlock(true);

	_needRefresh = true;
	AnimatedSprite::updatePosition();

}

void AsScene3010DeadBolt::update() {
	updateAnim();
	updatePosition();
	if (_countdown != 0 && (--_countdown == 0)) {
		stDisabled();
	}
}

uint32 AsScene3010DeadBolt::hmAnimation(int messageNum, const MessageParam &param, Entity *sender) {
	uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
	switch (messageNum) {
	case NM_ANIMATION_STOP:
		gotoNextState();
		break;
	default:
		break;
	}
	return messageResult;
}

void AsScene3010DeadBolt::stIdle() {
	stopAnimation();
	SetUpdateHandler(&AsScene3010DeadBolt::update);
	SetMessageHandler(&Sprite::handleMessage);
	_locked = false;
}

void AsScene3010DeadBolt::unlock(bool skipAnim) {
	if (!_unlocked) {
		setVisible(true);
		if (skipAnim) {
			startAnimation(kAsScene3010DeadBoltFileHashes1[_boltIndex], -1, 0);
			_newStickFrameIndex = STICK_LAST_FRAME;
		} else {
			startAnimation(kAsScene3010DeadBoltFileHashes1[_boltIndex], 0, -1);
			SetMessageHandler(&AsScene3010DeadBolt::hmAnimation);
			FinalizeState(&AsScene3010DeadBolt::stIdleMessage);
			NextState(&AsScene3010DeadBolt::stIdle);
			playSound(0);
		}
		_unlocked = true;
		loadSound(2, 0x4010C345);
	}
}

void AsScene3010DeadBolt::stIdleMessage() {
	stopAnimation();
	SetMessageHandler(&Sprite::handleMessage);
	sendMessage(_parentScene, 0x2001, _boltIndex);
}

void AsScene3010DeadBolt::lock() {
	if (!_locked) {
		_locked = true;
		setVisible(true);
		startAnimation(kAsScene3010DeadBoltFileHashes2[_boltIndex], 0, -1);
		SetMessageHandler(&AsScene3010DeadBolt::hmAnimation);
		FinalizeState(&AsScene3010DeadBolt::stDisabledMessage);
		NextState(&AsScene3010DeadBolt::stIdle);
		if (_soundToggle) {
			playSound(0);
		} else {
			playSound(1);
		}
		_soundToggle = !_soundToggle;
	}
}

void AsScene3010DeadBolt::setCountdown(int count) {
	_countdown = count * 18 + 1;
}

void AsScene3010DeadBolt::stDisabled() {
	setVisible(true);
	startAnimation(kAsScene3010DeadBoltFileHashes1[_boltIndex], 0, -1);
	SetMessageHandler(&AsScene3010DeadBolt::hmAnimation);
	FinalizeState(&AsScene3010DeadBolt::stDisabledMessage);
	NextState(&AsScene3010DeadBolt::stIdle);
	_playBackwards = true;
	playSound(2);
}

void AsScene3010DeadBolt::stDisabledMessage() {
	setVisible(false);
	sendMessage(_parentScene, 0x2003, _boltIndex);
}

// Scene3011

static const uint32 kAsScene3011SymbolFileHashes[] = {
	0x00C88050,
	0x01488050,
	0x02488050,
	0x04488050,
	0x08488050,
	0x10488050,
	0x20488050,
	0x40488050,
	0x80488050,
	0x00488051,
	0x00488052,
	0x00488054,
	0x008B0000,
	0x008D0000,
	0x00810000,
	0x00990000,
	0x00A90000,
	0x00C90000,
	0x00090000,
	0x01890000,
	0x02890000,
	0x04890000,
	0x08890000,
	0x10890000
};

SsScene3011Button::SsScene3011Button(NeverhoodEngine *vm, Scene *parentScene, bool flag)
	: StaticSprite(vm, 1400), _parentScene(parentScene), _countdown(0) {

	loadSprite(flag ? 0x11282020 : 0x994D0433, kSLFDefDrawOffset | kSLFDefPosition | kSLFDefCollisionBoundsOffset, 400);
	setVisible(false);
	loadSound(0, 0x44061000);
	SetUpdateHandler(&SsScene3011Button::update);
	SetMessageHandler(&SsScene3011Button::handleMessage);
}

void SsScene3011Button::update() {
	updatePosition();
	if (_countdown != 0 && (--_countdown == 0)) {
		setVisible(false);
	}
}

uint32 SsScene3011Button::handleMessage(int messageNum, const MessageParam &param, Entity *sender) {
	uint32 messageResult = 0;
	StaticSprite::handleMessage(messageNum, param, sender);
	switch (messageNum) {
	case 0x1011:
		if (_countdown == 0) {
			setVisible(true);
			_countdown = 4;
			sendMessage(_parentScene, 0x2000, 0);
			playSound(0);
		}
		messageResult = 1;
		break;
	default:
		break;
	}
	return messageResult;
}

AsScene3011Symbol::AsScene3011Symbol(NeverhoodEngine *vm, int symbolIndex, bool largeSymbol)
	: AnimatedSprite(vm, 1000), _symbolIndex(symbolIndex), _largeSymbol(largeSymbol), _isNoisy(false) {

	if (_largeSymbol) {
		_x = 310;
		_y = 200;
		createSurface1(kAsScene3011SymbolFileHashes[_symbolIndex], 1200);
		loadSound(0, 0x6052C60F);
		loadSound(1, 0x6890433B);
	} else {
		_symbolIndex = 12;
		_x = symbolIndex * 39 + 96;
		_y = 225;
		createSurface(1200, 41, 48);
		loadSound(0, 0x64428609);
		loadSound(1, 0x7080023B);
	}
	setVisible(false);
	_needRefresh = true;
	SetUpdateHandler(&AnimatedSprite::update);
}

void AsScene3011Symbol::show(bool isNoisy) {
	_isNoisy = isNoisy;
	startAnimation(kAsScene3011SymbolFileHashes[_symbolIndex], 0, -1);
	setVisible(true);
	if (_isNoisy) {
		playSound(1);
	} else {
		playSound(0);
	}
}

void AsScene3011Symbol::hide() {
	stopAnimation();
	setVisible(false);
}

void AsScene3011Symbol::stopSymbolSound() {
	if (_isNoisy) {
		stopSound(1);
	} else {
		stopSound(0);
	}
}

void AsScene3011Symbol::change(int symbolIndex, bool isNoisy) {
	_symbolIndex = symbolIndex;
	_isNoisy = isNoisy;
	startAnimation(kAsScene3011SymbolFileHashes[_symbolIndex], 0, -1);
	setVisible(true);
	if (_isNoisy) {
		playSound(1);
	} else {
		playSound(0);
	}
}

} // End of namespace Neverhood
