#include "stdafx.h"
#include "File.h"
#include "InputOutputStream.h"
#include "net.minecraft.world.entity.h"
#include "net.minecraft.world.level.h"
#include "net.minecraft.world.level.chunk.h"
#include "net.minecraft.world.level.tile.entity.h"
#include "net.minecraft.world.level.storage.h"
#include "FileHeader.h"
#include "OldChunkStorage.h"
DWORD OldChunkStorage::tlsIdx = 0;
OldChunkStorage::ThreadStorage *OldChunkStorage::tlsDefault = NULL;

OldChunkStorage::ThreadStorage::ThreadStorage()
{
	blockData = byteArray(Level::CHUNK_TILE_COUNT);
	dataData = byteArray(Level::HALF_CHUNK_TILE_COUNT);
	skyLightData = byteArray(Level::HALF_CHUNK_TILE_COUNT);
	blockLightData = byteArray(Level::HALF_CHUNK_TILE_COUNT);
}

OldChunkStorage::ThreadStorage::~ThreadStorage()
{
	delete [] blockData.data;
	delete [] dataData.data;
	delete [] skyLightData.data;
	delete [] blockLightData.data;
}

void OldChunkStorage::CreateNewThreadStorage()
{
	ThreadStorage *tls = new ThreadStorage();
	if(tlsDefault == NULL )
	{
		tlsIdx = TlsAlloc();
		tlsDefault = tls;
	}
	TlsSetValue(tlsIdx, tls);
}

void OldChunkStorage::UseDefaultThreadStorage()
{
	TlsSetValue(tlsIdx, tlsDefault);
}

void OldChunkStorage::ReleaseThreadStorage()
{
	ThreadStorage *tls = (ThreadStorage *)TlsGetValue(tlsIdx);
	if( tls == tlsDefault ) return;

	delete tls;
}

OldChunkStorage::OldChunkStorage(File dir, bool create)
{
	this->dir = dir;
	this->create = create;
}

File OldChunkStorage::getFile(int x, int z)
{
	wchar_t name[MAX_PATH_SIZE];
	wchar_t path1[MAX_PATH_SIZE];
	wchar_t path2[MAX_PATH_SIZE];

	wchar_t xRadix36[64];
	wchar_t zRadix36[64];
#if defined __PS3__
	assert(0);	// need a gcc verison of _itow ?
#else
	_itow(x,xRadix36,36);
	_itow(z,zRadix36,36);
	swprintf(name,MAX_PATH_SIZE,L"c.%ls.%ls.dat",xRadix36,zRadix36);
	_itow(x & 63,path1,36);
	_itow(z & 63,path2,36);
#endif
	//sprintf(file,"%s\\%s",dir,path1);
	File file( dir, wstring( path1 ) );
	if( !file.exists() )
	{
		if(create) file.mkdir();
		else
		{
			return File(L"");
		}
	}

	//strcat(file,"\\");
	//strcat(file,path2);
	file = File( file, wstring( path2 ) );
	if( !file.exists() )
	{
		if(create) file.mkdir();
		else
		{
			return File(L"");
		}
	}

	//strcat(file,"\\");
	//strcat(file,name);
	//sprintf(file,"%s\\%s",file,name);
	file = File( file, wstring( name ) );
	if ( !file.exists() )
	{
		if (!create) 
		{
			return File(L"");
		}
	}
	return file;
}

LevelChunk *OldChunkStorage::load(Level *level, int x, int z)
{
	File file = getFile(x, z);
	if (!file.getPath().empty() && file.exists())
	{
		// 4J - removed try/catch
		//		try {
		//                System.out.println("Loading chunk "+x+", "+z);
		FileInputStream fis = FileInputStream(file);
		CompoundTag *tag = NbtIo::readCompressed(&fis);
		if (!tag->contains(L"Level"))
		{
			char buf[256];
			sprintf(buf,"Chunk file at %d, %d is missing level data, skipping\n",x,z);
			app.DebugPrintf(buf);
			return NULL;
		}
		if (!tag->getCompound(L"Level")->contains(L"Blocks"))
		{
			char buf[256];
			sprintf(buf,"Chunk file at %d, %d is missing block data, skipping\n",x,z);
			app.DebugPrintf(buf);
			return NULL;
		}
		LevelChunk *levelChunk = OldChunkStorage::load(level, tag->getCompound(L"Level"));
		if (!levelChunk->isAt(x, z))
		{
			char buf[256];
			sprintf(buf,"Chunk fileat %d, %d is in the wrong location; relocating. Expected %d, %d, got %d, %d\n",
				x, z, x, z, levelChunk->x, levelChunk->z);
			app.DebugPrintf(buf);
			tag->putInt(L"xPos", x);
			tag->putInt(L"zPos", z);
			levelChunk = OldChunkStorage::load(level, tag->getCompound(L"Level"));
		}

		return levelChunk;
		//		} catch (Exception e) {
		//			e.printStackTrace();
		//		}
	}
	return NULL;
}

void OldChunkStorage::save(Level *level, LevelChunk *levelChunk)
{
	level->checkSession();
	File file = getFile(levelChunk->x, levelChunk->z);
	if (file.exists())
	{
		LevelData *levelData = level->getLevelData();
		levelData->setSizeOnDisk( levelData->getSizeOnDisk() - file.length() );
	} 

	// 4J - removed try/catch
	//    try {
	//char tmpFileName[MAX_PATH_SIZE];
	//sprintf(tmpFileName,"%s\\%s",dir,"tmp_chunk.dat");
	File tmpFile( dir, L"tmp_chunk.dat" );
	//            System.out.println("Saving chunk "+levelChunk.x+", "+levelChunk.z);

	FileOutputStream fos = FileOutputStream(tmpFile);
	CompoundTag *tag = new CompoundTag();
	CompoundTag *levelData = new CompoundTag();
	tag->put(L"Level", levelData);
	OldChunkStorage::save(levelChunk, level, levelData);
	NbtIo::writeCompressed(tag, &fos);
	fos.close();

	if (file.exists())
	{
		//DeleteFile(file);
		file._delete();
	}
	//MoveFile(tmpFile,file);
	tmpFile.renameTo( file );

	LevelData *levelInfo = level->getLevelData();
	levelInfo->setSizeOnDisk(levelInfo->getSizeOnDisk() + file.length() );
	//    } catch (Exception e) {
	//        e.printStackTrace();
	//    }
}

bool OldChunkStorage::saveEntities(LevelChunk *lc, Level *level, CompoundTag *tag)
{
	// If we saved and it had no entities, and nothing has been added since skip this one
	if(!lc->lastSaveHadEntities) return false;

	lc->lastSaveHadEntities = false;
	ListTag<CompoundTag> *entityTags = new ListTag<CompoundTag>();
	
#ifdef _ENTITIES_RW_SECTION
	EnterCriticalRWSection(&lc->m_csEntities, true);
#else
	EnterCriticalSection(&lc->m_csEntities);
#endif
	for (int i = 0; i < lc->ENTITY_BLOCKS_LENGTH; i++)
	{
		AUTO_VAR(itEnd, lc->entityBlocks[i]->end());
		for( vector<shared_ptr<Entity> >::iterator it = lc->entityBlocks[i]->begin(); it != itEnd; it++ )
		{
			shared_ptr<Entity> e = *it;
			lc->lastSaveHadEntities = true;
			CompoundTag *teTag = new CompoundTag();
			if (e->save(teTag))
			{
				entityTags->add(teTag);
			}

		}
	}
#ifdef _ENTITIES_RW_SECTION
	LeaveCriticalRWSection(&lc->m_csEntities, true);
#else
	LeaveCriticalSection(&lc->m_csEntities);
#endif

	tag->put(L"Entities", entityTags);

	return lc->lastSaveHadEntities;
}

void OldChunkStorage::save(LevelChunk *lc, Level *level, DataOutputStream *dos)
{
	dos->writeShort(SAVE_FILE_VERSION_NUMBER);
	dos->writeInt(lc->x);
	dos->writeInt(lc->z);
	dos->writeLong(level->getTime());

	PIXBeginNamedEvent(0,"Getting block data");
	lc->writeCompressedBlockData(dos);
	PIXEndNamedEvent();

	PIXBeginNamedEvent(0,"Getting data data");
	lc->writeCompressedDataData(dos);
	PIXEndNamedEvent();

	PIXBeginNamedEvent(0,"Getting sky and block light data");
	lc->writeCompressedSkyLightData(dos);
	lc->writeCompressedBlockLightData(dos);
	PIXEndNamedEvent();

	dos->write(lc->heightmap);
	dos->writeShort(lc->terrainPopulated);
	dos->write(lc->getBiomes());

	PIXBeginNamedEvent(0,"Saving entities");
	CompoundTag *tag = new CompoundTag();
#ifndef SPLIT_SAVES
	saveEntities(lc, level, tag);
#endif
	
	PIXBeginNamedEvent(0,"Saving tile entities");
	ListTag<CompoundTag> *tileEntityTags = new ListTag<CompoundTag>();

	AUTO_VAR(itEnd, lc->tileEntities.end());
	for( unordered_map<TilePos, shared_ptr<TileEntity>, TilePosKeyHash, TilePosKeyEq>::iterator it = lc->tileEntities.begin();
		it != itEnd; it++)
	{
		shared_ptr<TileEntity> te = it->second;
		CompoundTag *teTag = new CompoundTag();
		te->save(teTag);
		tileEntityTags->add(teTag);
	}
	tag->put(L"TileEntities", tileEntityTags);
	PIXEndNamedEvent();

	PIXBeginNamedEvent(0,"Saving tile tick data");
	vector<TickNextTickData > *ticksInChunk = level->fetchTicksInChunk(lc, false);
	if (ticksInChunk != NULL)
	{
		__int64 levelTime = level->getTime();

		ListTag<CompoundTag> *tickTags = new ListTag<CompoundTag>();
		for( int i = 0; i < ticksInChunk->size(); i++ )
		{
			TickNextTickData td = ticksInChunk->at(i);
			CompoundTag *teTag = new CompoundTag();
			teTag->putInt(L"i", td.tileId);
			teTag->putInt(L"x", td.x);
			teTag->putInt(L"y", td.y);
			teTag->putInt(L"z", td.z);
			teTag->putInt(L"t", (int) (td.m_delay - levelTime));

			tickTags->add(teTag);
		}
		tag->put(L"TileTicks", tickTags);
	}
	delete ticksInChunk;
	PIXEndNamedEvent();

	NbtIo::write(tag,dos);
	delete tag;
	PIXEndNamedEvent();
}

void OldChunkStorage::save(LevelChunk *lc, Level *level, CompoundTag *tag)
{
	level->checkSession();
	tag->putInt(L"xPos", lc->x);
	tag->putInt(L"zPos", lc->z);
	tag->putLong(L"LastUpdate", level->getTime());
	// 4J - changes here for new storage. Now have static storage for getting lighting data for block, data, and sky & block lighting. This
	// wasn't required in the original version as we could just reference the information in the level itself, but with our new storage system
	// the full  data doesn't normally exist & so getSkyLightData/getBlockLightData etc. need somewhere to output this data. Making this static so
	// that we aren't dynamically allocating memory in the server thread when writing chunks as this causes serious stalling on the main thread.
	// Will be fine so long as we only actually create tags for once chunk at a time.

	// 4J Stu - As we now save on multiple threads, the static data has been moved to TLS
	ThreadStorage *tls = (ThreadStorage *)TlsGetValue(tlsIdx);

	PIXBeginNamedEvent(0,"Getting block data");
	//static byteArray blockData = byteArray(32768);
	lc->getBlockData(tls->blockData);
	tag->putByteArray(L"Blocks", tls->blockData);
	PIXEndNamedEvent();

	PIXBeginNamedEvent(0,"Getting data data");
	//static byteArray dataData = byteArray(16384);
	lc->getDataData(tls->dataData);
	tag->putByteArray(L"Data", tls->dataData);
	PIXEndNamedEvent();

	PIXBeginNamedEvent(0,"Getting sky and block light data");
	//static byteArray skyLightData = byteArray(16384);
	//static byteArray blockLightData = byteArray(16384);
	lc->getSkyLightData(tls->skyLightData);
	lc->getBlockLightData(tls->blockLightData);
	tag->putByteArray(L"SkyLight", tls->skyLightData);
	tag->putByteArray(L"BlockLight", tls->blockLightData);
	PIXEndNamedEvent();

	tag->putByteArray(L"HeightMap", lc->heightmap);
	tag->putShort(L"TerrainPopulatedFlags", lc->terrainPopulated);		// 4J - changed from "TerrainPopulated" to "TerrainPopulatedFlags" as now stores a bitfield, java stores a bool
	tag->putByteArray(L"Biomes", lc->getBiomes());

	PIXBeginNamedEvent(0,"Saving entities");
#ifndef SPLIT_SAVES
	saveEntities(lc, level, tag);
#endif

	PIXBeginNamedEvent(0,"Saving tile entities");
	ListTag<CompoundTag> *tileEntityTags = new ListTag<CompoundTag>();

	AUTO_VAR(itEnd, lc->tileEntities.end());
	for( unordered_map<TilePos, shared_ptr<TileEntity>, TilePosKeyHash, TilePosKeyEq>::iterator it = lc->tileEntities.begin();
		it != itEnd; it++)
	{
		shared_ptr<TileEntity> te = it->second;
		CompoundTag *teTag = new CompoundTag();
		te->save(teTag);
		tileEntityTags->add(teTag);
	}
	tag->put(L"TileEntities", tileEntityTags);
	PIXEndNamedEvent();

	PIXBeginNamedEvent(0,"Saving tile tick data");
	vector<TickNextTickData > *ticksInChunk = level->fetchTicksInChunk(lc, false);
	if (ticksInChunk != NULL)
	{
		__int64 levelTime = level->getTime();

		ListTag<CompoundTag> *tickTags = new ListTag<CompoundTag>();
		for( int i = 0; i < ticksInChunk->size(); i++ )
		{
			TickNextTickData td = ticksInChunk->at(i);
			CompoundTag *teTag = new CompoundTag();
			teTag->putInt(L"i", td.tileId);
			teTag->putInt(L"x", td.x);
			teTag->putInt(L"y", td.y);
			teTag->putInt(L"z", td.z);
			teTag->putInt(L"t", (int) (td.m_delay - levelTime));

			tickTags->add(teTag);
		}
		tag->put(L"TileTicks", tickTags);
	}
	delete ticksInChunk;
	PIXEndNamedEvent();
	PIXEndNamedEvent();
}

void OldChunkStorage::loadEntities(LevelChunk *lc, Level *level, CompoundTag *tag)
{
	ListTag<CompoundTag> *entityTags = (ListTag<CompoundTag> *) tag->getList(L"Entities");
	if (entityTags != NULL)
	{
		for (int i = 0; i < entityTags->size(); i++)
		{
			CompoundTag *teTag = entityTags->get(i);
			shared_ptr<Entity> te = EntityIO::loadStatic(teTag, level);
			lc->lastSaveHadEntities = true;
			if (te != NULL)
			{
				lc->addEntity(te);
			}
		}
	}

	ListTag<CompoundTag> *tileEntityTags = (ListTag<CompoundTag> *) tag->getList(L"TileEntities");
	if (tileEntityTags != NULL)
	{
		for (int i = 0; i < tileEntityTags->size(); i++)
		{
			CompoundTag *teTag = tileEntityTags->get(i);
			shared_ptr<TileEntity> te = TileEntity::loadStatic(teTag);
			if (te != NULL)
			{
				lc->addTileEntity(te);
			}
		}
	}
}

LevelChunk *OldChunkStorage::load(Level *level, DataInputStream *dis)
{
	short version = dis->readShort();
	int x = dis->readInt();
	int z = dis->readInt();
	int time = dis->readLong();

	LevelChunk *levelChunk = new LevelChunk(level, x, z);

	levelChunk->readCompressedBlockData(dis);
	levelChunk->readCompressedDataData(dis);
	levelChunk->readCompressedSkyLightData(dis);
	levelChunk->readCompressedBlockLightData(dis);

	dis->readFully(levelChunk->heightmap);

	levelChunk->terrainPopulated = dis->readShort();	
	// If all neighbours have been post-processed, then we should have done the post-post-processing now. Check that this is set as if it isn't then we won't be able
	// to send network data for chunks, and we won't ever try and set it again as all the directional flags are now already set - should only be an issue for old maps
	// before this flag was added.
	if( ( levelChunk->terrainPopulated & LevelChunk::sTerrainPopulatedAllNeighbours ) == LevelChunk::sTerrainPopulatedAllNeighbours )
	{
		levelChunk->terrainPopulated |= LevelChunk::sTerrainPostPostProcessed;
	}

	dis->readFully(levelChunk->biomes);

	CompoundTag *tag = NbtIo::read(dis);

	loadEntities(levelChunk, level, tag);

	if (tag->contains(L"TileTicks"))
	{
		ListTag<CompoundTag> *tileTicks = (ListTag<CompoundTag> *) tag->getList(L"TileTicks");

		if (tileTicks != NULL)
		{
			for (int i = 0; i < tileTicks->size(); i++)
			{
				CompoundTag *teTag = tileTicks->get(i);

				level->forceAddTileTick(teTag->getInt(L"x"), teTag->getInt(L"y"), teTag->getInt(L"z"), teTag->getInt(L"i"), teTag->getInt(L"t"));
			}
		}
	}

	delete tag;

	return levelChunk;
}

LevelChunk *OldChunkStorage::load(Level *level, CompoundTag *tag)
{
	int x = tag->getInt(L"xPos");
	int z = tag->getInt(L"zPos");

	LevelChunk *levelChunk = new LevelChunk(level, x, z);
	// 4J - the original code uses the data in the tag directly, but this is now just used as a source when creating the compressed data, so
	// we need to free up the data in the tag once we are done
	levelChunk->setBlockData(tag->getByteArray(L"Blocks"));
	delete [] tag->getByteArray(L"Blocks").data;
	//	levelChunk->blocks = tag->getByteArray(L"Blocks");

	// 4J - the original code uses the data in the tag directly, but this is now just used as a source when creating the compressed data, so
	// we need to free up the data in the tag once we are done
	levelChunk->setDataData(tag->getByteArray(L"Data"));
	delete [] tag->getByteArray(L"Data").data;

	// 4J - changed to use our new methods for accessing lighting
	levelChunk->setSkyLightData(tag->getByteArray(L"SkyLight"));
	levelChunk->setBlockLightData(tag->getByteArray(L"BlockLight"));

	// In the original code (commented out below) constructing DataLayers from these arrays uses the data directly and so it doesn't need deleted. The new
	// setSkyLightData/setBlockLightData take a copy of the data so we need to delete the local one now
	delete [] tag->getByteArray(L"SkyLight").data;
	delete [] tag->getByteArray(L"BlockLight").data;

	//	levelChunk->skyLight = new DataLayer(tag->getByteArray(L"SkyLight"), level->depthBits);
	//	levelChunk->blockLight = new DataLayer(tag->getByteArray(L"BlockLight"), level->depthBits);

	delete [] levelChunk->heightmap.data;
	levelChunk->heightmap = tag->getByteArray(L"HeightMap");
	// 4J - TerrainPopulated was a bool (java), then changed to be a byte bitfield, then replaced with TerrainPopulatedShort to store a wider bitfield
	if( tag->get(L"TerrainPopulated") )
	{
		// Java bool type or byte bitfield
		levelChunk->terrainPopulated = tag->getByte(L"TerrainPopulated");		
		if( levelChunk->terrainPopulated >= 1 ) levelChunk->terrainPopulated = LevelChunk::sTerrainPopulatedAllNeighbours | LevelChunk::sTerrainPostPostProcessed;	// Convert from old bool type to new bitfield
	}
	else
	{
		// New style short
		levelChunk->terrainPopulated = tag->getShort(L"TerrainPopulatedFlags");	
		// If all neighbours have been post-processed, then we should have done the post-post-processing now. Check that this is set as if it isn't then we won't be able
		// to send network data for chunks, and we won't ever try and set it again as all the directional flags are now already set - should only be an issue for old maps
		// before this flag was added.
		if( ( levelChunk->terrainPopulated & LevelChunk::sTerrainPopulatedAllNeighbours ) == LevelChunk::sTerrainPopulatedAllNeighbours )
		{
			levelChunk->terrainPopulated |= LevelChunk::sTerrainPostPostProcessed;
		}
	}

#if 0
	// 4J - removed - we shouldn't need this any more
	if (!levelChunk->data->isValid())
	{
		levelChunk->data = new DataLayer(LevelChunk::BLOCKS_LENGTH, level->depthBits);		// 4J - BLOCKS_LENGTH was levelChunk->blocks.length
	}
#endif

	// 4J removed - we shouldn't need this any more
#if 0
	if (levelChunk->heightmap.data == NULL || !levelChunk->skyLight->isValid())
	{
		static int chunksUpdated = 0;
		delete [] levelChunk->heightmap.data;
		levelChunk->heightmap = byteArray(16 * 16);
		delete levelChunk->skyLight;
		levelChunk->skyLight = new DataLayer(levelChunk->blocks.length, level->depthBits);
		levelChunk->recalcHeightmap();
	}

	if (!levelChunk->blockLight->isValid())
	{
		delete levelChunk->blockLight;
		levelChunk->blockLight = new DataLayer(levelChunk->blocks.length, level->depthBits);
		levelChunk->recalcBlockLights();
	}
#endif

	if (tag->contains(L"Biomes"))
	{
		levelChunk->setBiomes(tag->getByteArray(L"Biomes"));
	}

	loadEntities(levelChunk, level, tag);

	if (tag->contains(L"TileTicks"))
	{
		ListTag<CompoundTag> *tileTicks = (ListTag<CompoundTag> *) tag->getList(L"TileTicks");

		if (tileTicks != NULL)
		{
			for (int i = 0; i < tileTicks->size(); i++)
			{
				CompoundTag *teTag = tileTicks->get(i);

				level->forceAddTileTick(teTag->getInt(L"x"), teTag->getInt(L"y"), teTag->getInt(L"z"), teTag->getInt(L"i"), teTag->getInt(L"t"));
			}
		}
	}

	return levelChunk;
}

void OldChunkStorage::tick()
{
}

void OldChunkStorage::flush()
{
}

void OldChunkStorage::saveEntities(Level *level, LevelChunk *levelChunk)
{
}
