#include "DimensionGenerator.h"
#include "Constants.h"
#include "Noise.h"
#include "NoBlock.h"

#include <iostream>


DimensionGenerator::DimensionGenerator( int dimensionId )
    : ReferenceCounter(),
    dimensionId( dimensionId ),
    minTemplateAffectedPosition( 0, 0, 0 ),
    maxTemplateAffectedPosition( 0, 0, 0 )
{
    StaticRegistry<DimensionGenerator>::INSTANCE.registerT( this, dimensionId );
}

DimensionGenerator::~DimensionGenerator()
{}

BiomGenerator* DimensionGenerator::zBiomGenerator( int seed, int x, int y )
{
    double noise = zBiomNoise( seed )->getNoise( (double)x, (double)y, 0.0 );
    double border = 0;
    BiomGenerator* gen = 0;
    auto genI = biomGenerators.begin();
    auto distI = biomDistribution.begin();
    do
    {
        border += (double)distI++;
        gen = genI++;
    } while( border < noise && (bool)distI && (bool)genI );
    return gen;
}

void DimensionGenerator::registerBiom( BiomGenerator* generator, double possibility )
{
    biomGenerators.add( generator );
    biomDistribution.add( possibility );
    for( auto t : generator->getTemplates() )
    {
        minTemplateAffectedPosition.x = MIN( minTemplateAffectedPosition.x, t->getMinAffectedOffset().x );
        minTemplateAffectedPosition.y = MIN( minTemplateAffectedPosition.y, t->getMinAffectedOffset().y );
        minTemplateAffectedPosition.z = MIN( minTemplateAffectedPosition.z, t->getMinAffectedOffset().z );
        maxTemplateAffectedPosition.x = MAX( maxTemplateAffectedPosition.x, t->getMaxAffectedOffset().x );
        maxTemplateAffectedPosition.y = MAX( maxTemplateAffectedPosition.y, t->getMaxAffectedOffset().y );
        maxTemplateAffectedPosition.z = MAX( maxTemplateAffectedPosition.z, t->getMaxAffectedOffset().z );
    }
}

Framework::RCArray<GeneratedStructure>* DimensionGenerator::getGeneratedStructoresForArea( int seed, Framework::Vec3<int> minPos, Framework::Vec3<int> maxPos )
{
    Framework::RCArray<GeneratedStructure>* result = new Framework::RCArray<GeneratedStructure>();

    int minSearchX = minPos.x - maxTemplateAffectedPosition.x;
    int minSearchY = minPos.y - maxTemplateAffectedPosition.y;
    int minSearchZ = MAX( minPos.z - maxTemplateAffectedPosition.z, 0 );
    int maxSearchX = maxPos.x - minTemplateAffectedPosition.x;
    int maxSearchY = maxPos.y - minTemplateAffectedPosition.y;
    int maxSearchZ = MIN( maxPos.z - minTemplateAffectedPosition.z, WORLD_HEIGHT - 1 );

    Noise* structureNoise = zStructureNoise( seed + dimensionId );
    for( int x = minSearchX; x <= maxSearchX; x++ )
    {
        for( int y = minSearchY; y <= maxSearchY; y++ )
        {
            BiomGenerator* biom = zBiomGenerator( seed + dimensionId, x, y );
            int height = MIN_AIR_LEVEL + (int)(biom->zHeightMapNoise( seed + dimensionId )->getNoise( (double)(x), (double)(y), 0.0 ) * (MAX_AIR_LEVEL - MIN_AIR_LEVEL));
            for( int z = minSearchZ; z <= maxSearchZ; z++ )
            {
                if( z < height )
                {
                    double rValue = structureNoise->getNoise( (double)x, (double)y, (double)z );
                    double probSum = 0;
                    for( auto t : biom->getTemplates() )
                    {
                        if( t->isGenerationPossable( Framework::Vec3<int>( x, y, z ), height - z ) )
                        {
                            if( rValue - probSum <= t->getPropability() )
                            {
                                result->add( t->generateAt( Framework::Vec3<int>( x, y, z ), structureNoise ) );
                                break;
                            }
                        }
                        probSum += t->getPropability();
                    }
                }
            }
        }
    }

    return result;
}

Chunk* DimensionGenerator::generateChunk( int seed, int centerX, int centerY )
{
    Framework::RCArray<GeneratedStructure>* structures = getGeneratedStructoresForArea( seed, Framework::Vec3<int>( centerX - CHUNK_SIZE / 2, centerY - CHUNK_SIZE / 2, 0 ), Framework::Vec3<int>( centerX + CHUNK_SIZE / 2, centerY + CHUNK_SIZE / 2, WORLD_HEIGHT - 1 ) );
    std::cout << "generating chunk " << centerX << ", " << centerY << "\n";
    Chunk* chunk = new Chunk( Framework::Punkt( centerX, centerY ), dimensionId );
    for( int x = -CHUNK_SIZE / 2; x < CHUNK_SIZE / 2; x++ )
    {
        for( int y = -CHUNK_SIZE / 2; y < CHUNK_SIZE / 2; y++ )
        {
            BiomGenerator* biom = zBiomGenerator( seed + dimensionId, x + centerX, y + centerY );
            // TODO: use Noise interpolator for height map between different bioms
            int height = MIN_AIR_LEVEL + (int)(biom->zHeightMapNoise( seed + dimensionId )->getNoise( (double)(x + centerX), (double)(y + centerY), 0.0 ) * (MAX_AIR_LEVEL - MIN_AIR_LEVEL));
            int maxSurfaceHeight = (int)(MAX_SURFACE_HEIGHT * (1.f - (float)(height - MIN_AIR_LEVEL) / (float)(MAX_AIR_LEVEL - MIN_AIR_LEVEL)));
            int actualSurfaceHeight = (int)((float)maxSurfaceHeight * (1.f - VARIABLE_SURFACE_PART) + ((float)maxSurfaceHeight * VARIABLE_SURFACE_PART * (float)biom->zHeightMapNoise( seed + dimensionId )->getNoise( (double)(x + centerX), (double)(y + centerY), 10.0 )));
            for( int z = 0; z < WORLD_HEIGHT; z++ )
            {
                Framework::Either<Block*, int> generated = AirBlockBlockType::ID;
                bool structureAffected = 0;
                for( auto structure : *structures )
                {
                    if( structure->isBlockAffected( Framework::Vec3<int>( x + centerX, y + centerY, z ) ) )
                    {
                        generated = structure->generateBlockAt( Framework::Vec3<int>( x + centerX, y + centerY, z ) );
                        structureAffected = 1;
                        break;
                    }
                }
                if( !structureAffected )
                {
                    if( z < height && z >= height - actualSurfaceHeight )
                        generated = biom->generateSurfaceBlock( x + centerX, y + centerY, z );
                    else if( z < height )
                        generated = biom->generateBelowSurfaceBlock( x + centerX, y + centerY, z );
                }
                if( generated.isA() )
                    chunk->putBlockAt( Framework::Vec3<int>( x + CHUNK_SIZE / 2, y + CHUNK_SIZE / 2, z ), generated );
                else
                    chunk->putBlockTypeAt( Framework::Vec3<int>( x + CHUNK_SIZE / 2, y + CHUNK_SIZE / 2, z ), generated );
            }
        }
    }
    structures->release();
    return chunk;
}

Framework::Either<Block*, int> DimensionGenerator::generateBlock( int seed, Framework::Vec3<int> location )
{
    Framework::RCArray<GeneratedStructure>* structures = getGeneratedStructoresForArea( seed, location, location );
    BiomGenerator* biom = zBiomGenerator( seed + dimensionId, location.x, location.y );
    // TODO: use Noise interpolator for height map between different bioms
    int height = MIN_AIR_LEVEL + (int)(biom->zHeightMapNoise( seed + dimensionId )->getNoise( (double)(location.x), (double)(location.y), 0.0 ) * (MAX_AIR_LEVEL - MIN_AIR_LEVEL));
    int maxSurfaceHeight = (int)(MAX_SURFACE_HEIGHT * (1.f - (float)(height - MIN_AIR_LEVEL) / (float)(MAX_AIR_LEVEL - MIN_AIR_LEVEL)));
    int actualSurfaceHeight = (int)((float)maxSurfaceHeight * (1.f - VARIABLE_SURFACE_PART) + ((float)maxSurfaceHeight * VARIABLE_SURFACE_PART * (float)biom->zHeightMapNoise( seed + dimensionId )->getNoise( (double)(location.x), (double)(location.y), 10.0 )));
    for( auto structure : *structures )
    {
        if( structure->isBlockAffected( location ) )
        {
            auto generated = structure->generateBlockAt( location );
            structures->release();
            return generated;
        }
    }
    structures->release();
    if( location.z < height && location.z >= height - actualSurfaceHeight )
        return biom->generateSurfaceBlock( location.x, location.y, location.z );
    else if( location.z < height )
        return biom->generateBelowSurfaceBlock( location.x, location.y, location.z );
    return AirBlockBlockType::ID;
}

int DimensionGenerator::getDimensionId() const
{
    return dimensionId;
}