package client.renderer.chunk;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;

import org.lwjgl.opengl.GL11;

import client.Client;
import client.renderer.DefaultVertexFormats;
import client.renderer.Renderer;
import client.renderer.RegionRenderCache;
import client.renderer.RenderBuffer;
import client.renderer.VertexBuffer;
import client.renderer.tileentity.SpecialRenderer;
import client.renderer.tileentity.ElementRenderer;
import common.block.Block;
import common.block.ITileEntityProvider;
import common.collect.Maps;
import common.collect.Sets;
import common.init.Blocks;
import common.tileentity.TileEntity;
import common.util.BlockPos;
import common.util.BoundingBox;
import common.util.Facing;
import common.world.State;

public class RenderChunk
{
    private Client gm;
    private final Renderer renderGlobal;
    public static int renderChunksUpdated;
    private BlockPos position;
    public CompiledChunk compiledChunk = CompiledChunk.DUMMY;
    private final ReentrantLock lockCompileTask = new ReentrantLock();
    private final ReentrantLock lockCompiledChunk = new ReentrantLock();
    private ChunkCompileTaskGenerator compileTask = null;
    private final Set<TileEntity> setTileEntities = Sets.<TileEntity>newHashSet();
    private final int index;
    private final FloatBuffer modelviewMatrix = ByteBuffer.allocateDirect(16 << 2).order(ByteOrder.nativeOrder()).asFloatBuffer();
    private final VertexBuffer[] vertexBuffers = new VertexBuffer[BlockLayer.values().length];
    public BoundingBox boundingBox;
    private int frameIndex = -1;
    private boolean needsUpdate = true;
    private EnumMap<Facing, BlockPos> mapEnumFacing = Maps.newEnumMap(Facing.class);

    public RenderChunk(Client worldIn, Renderer renderGlobalIn, BlockPos blockPosIn, int indexIn)
    {
        this.gm = worldIn;
        this.renderGlobal = renderGlobalIn;
        this.index = indexIn;

        if (!blockPosIn.equals(this.getPosition()))
        {
            this.setPosition(blockPosIn);
        }
        
        for (int i = 0; i < BlockLayer.values().length; ++i)
        {
            this.vertexBuffers[i] = new VertexBuffer(DefaultVertexFormats.BLOCK);
        }
    }

    public boolean setFrameIndex(int frameIndexIn)
    {
        if (this.frameIndex == frameIndexIn)
        {
            return false;
        }
        else
        {
            this.frameIndex = frameIndexIn;
            return true;
        }
    }

    public VertexBuffer getVertexBufferByLayer(int layer)
    {
        return this.vertexBuffers[layer];
    }

    public void setPosition(BlockPos pos)
    {
        this.stopCompileTask();
        this.position = pos;
        this.boundingBox = new BoundingBox(pos, pos.add(16, 16, 16));

        for (Facing enumfacing : Facing.values())
        {
            this.mapEnumFacing.put(enumfacing, pos.offset(enumfacing, 16));
        }

        this.initModelviewMatrix();
    }

    public void rebuildChunk(float x, float y, float z, ChunkCompileTaskGenerator generator)
    {
        CompiledChunk compiledchunk = new CompiledChunk();
        int i = 1;
        BlockPos blockpos = this.position;
        BlockPos blockpos1 = blockpos.add(15, 15, 15);
        generator.getLock().lock();
        RegionRenderCache iblockaccess;

        try
        {
            if (generator.getStatus() != ChunkCompileTaskGenerator.Status.COMPILING)
            {
                return;
            }

            iblockaccess = new RegionRenderCache(this.gm, blockpos.add(-1, -1, -1), blockpos1.add(1, 1, 1), 1);
            generator.setCompiledChunk(compiledchunk);
        }
        finally
        {
            generator.getLock().unlock();
        }

        VisGraph lvt_10_1_ = new VisGraph();
        HashSet forced = Sets.newHashSet();

        if (!iblockaccess.isEmpty())
        {
            ++renderChunksUpdated;
            boolean[] aboolean = new boolean[BlockLayer.values().length];
            Renderer renderer = this.gm.renderer;

            for (BlockPos.MutableBlockPos blockpos$mutableblockpos : BlockPos.getAllInBoxMutable(blockpos, blockpos1))
            {
                State iblockstate = iblockaccess.getState(blockpos$mutableblockpos);
                Block block = iblockstate.getBlock();

                if (block.isOpaqueCube())
                {
                    lvt_10_1_.func_178606_a(blockpos$mutableblockpos);
                }

                if (block instanceof ITileEntityProvider)
                {
                    TileEntity tileentity = iblockaccess.getTileEntity(new BlockPos(blockpos$mutableblockpos));
                    ElementRenderer<TileEntity> tileentityspecialrenderer = SpecialRenderer.instance.<TileEntity>getRenderer(tileentity);

                    if (tileentity != null && tileentityspecialrenderer != null)
                    {
                        compiledchunk.addTileEntity(tileentity);

                        if (tileentityspecialrenderer.isAlwaysRendered())
                        {
                            forced.add(tileentity);
                        }
                    }
                }

                if (block != Blocks.air)
                {
                    BlockLayer layer = block.hasTransparency() ? BlockLayer.CUTOUT : BlockLayer.SOLID;
                    int idx = layer.ordinal();
                    RenderBuffer worldrenderer = generator.getRegionRenderCacheBuilder().getWorldRendererByLayerId(idx);

                    if (!compiledchunk.isLayerStarted(layer))
                    {
                        compiledchunk.setLayerStarted(layer);
                        this.preRenderBlocks(worldrenderer, blockpos);
                    }

                    aboolean[idx] |= renderer.renderBlock(iblockstate, blockpos$mutableblockpos, iblockaccess, worldrenderer);
                }
            }

            for (BlockLayer enumworldblocklayer : BlockLayer.values())
            {
                if (aboolean[enumworldblocklayer.ordinal()])
                {
                    compiledchunk.setLayerUsed(enumworldblocklayer);
                }

                if (compiledchunk.isLayerStarted(enumworldblocklayer))
                {
                	generator.getRegionRenderCacheBuilder().getWorldRendererByLayer(enumworldblocklayer).finishDrawing();
                }
            }
        }

        compiledchunk.setVisibility(lvt_10_1_.computeVisibility());
        this.lockCompileTask.lock();

        try
        {
            Set<TileEntity> set = Sets.newHashSet(forced);
            Set<TileEntity> set1 = Sets.newHashSet(this.setTileEntities);
            set.removeAll(this.setTileEntities);
            set1.removeAll(forced);
            this.setTileEntities.clear();
            this.setTileEntities.addAll(forced);
            this.renderGlobal.updateTileEntities(set1, set);
        }
        finally
        {
            this.lockCompileTask.unlock();
        }
    }

    protected void finishCompileTask()
    {
        this.lockCompileTask.lock();

        try
        {
            if (this.compileTask != null && this.compileTask.getStatus() != ChunkCompileTaskGenerator.Status.DONE)
            {
                this.compileTask.finish();
                this.compileTask = null;
            }
        }
        finally
        {
            this.lockCompileTask.unlock();
        }
    }

    public ReentrantLock getLockCompileTask()
    {
        return this.lockCompileTask;
    }

    public ChunkCompileTaskGenerator makeCompileTaskChunk()
    {
        this.lockCompileTask.lock();
        ChunkCompileTaskGenerator chunkcompiletaskgenerator;

        try
        {
            this.finishCompileTask();
            this.compileTask = new ChunkCompileTaskGenerator(this);
            chunkcompiletaskgenerator = this.compileTask;
        }
        finally
        {
            this.lockCompileTask.unlock();
        }

        return chunkcompiletaskgenerator;
    }

    private void preRenderBlocks(RenderBuffer worldRendererIn, BlockPos pos)
    {
        worldRendererIn.begin(GL11.GL_QUADS, DefaultVertexFormats.BLOCK);
        worldRendererIn.setTranslation((double)(-pos.getX()), (double)(-pos.getY()), (double)(-pos.getZ()));
    }

    private void initModelviewMatrix()
    {
        GL11.glPushMatrix();
        GL11.glLoadIdentity();
        float f = 1.000001F;
        GL11.glTranslatef(-8.0F, -8.0F, -8.0F);
        GL11.glScalef(f, f, f);
        GL11.glTranslatef(8.0F, 8.0F, 8.0F);
        GL11.glGetFloatv(2982, this.modelviewMatrix);
        GL11.glPopMatrix();
    }

    public void multModelviewMatrix()
    {
        GL11.glMultMatrixf(this.modelviewMatrix);
    }

    public CompiledChunk getCompiledChunk()
    {
        return this.compiledChunk;
    }

    public void setCompiledChunk(CompiledChunk compiledChunkIn)
    {
        this.lockCompiledChunk.lock();

        try
        {
            this.compiledChunk = compiledChunkIn;
        }
        finally
        {
            this.lockCompiledChunk.unlock();
        }
    }

    public void stopCompileTask()
    {
        this.finishCompileTask();
        this.compiledChunk = CompiledChunk.DUMMY;
    }

    public void deleteGlResources()
    {
        this.stopCompileTask();
        this.gm = null;

        for (int i = 0; i < BlockLayer.values().length; ++i)
        {
            if (this.vertexBuffers[i] != null)
            {
                this.vertexBuffers[i].deleteGlBuffers();
            }
        }
    }

    public BlockPos getPosition()
    {
        return this.position;
    }

    public void setNeedsUpdate(boolean needsUpdateIn)
    {
        this.needsUpdate = needsUpdateIn;
    }

    public boolean isNeedsUpdate()
    {
        return this.needsUpdate;
    }

    public BlockPos getBlockPosOffset16(Facing p_181701_1_)
    {
        return (BlockPos)this.mapEnumFacing.get(p_181701_1_);
    }
}
