/*
 * Copyright 2012, 2013 Hannes Janetzek
 * Copyright 2016 devemux86
 *
 * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
 *
 * This program is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.oscim.renderer.bucket;

import org.oscim.backend.GL;
import org.oscim.renderer.GLShader;
import org.oscim.renderer.GLState;
import org.oscim.renderer.GLViewport;
import org.oscim.renderer.MapRenderer;
import org.oscim.renderer.bucket.TextureItem.TexturePool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.ShortBuffer;

import static org.oscim.backend.GLAdapter.gl;
import static org.oscim.renderer.MapRenderer.COORD_SCALE;
import static org.oscim.renderer.MapRenderer.MAX_INDICES;

public class TextureBucket extends RenderBucket {

    static final Logger log = LoggerFactory.getLogger(TextureBucket.class);

    public static final int INDICES_PER_SPRITE = 6;
    static final int VERTICES_PER_SPRITE = 4;
    static final int SHORTS_PER_VERTICE = 6;

    public static final int TEXTURE_HEIGHT = 256;
    public static final int TEXTURE_WIDTH = 1024;
    static final int POOL_FILL = 4;

    /**
     * pool shared by TextLayers
     */
    public static final TexturePool pool = new TexturePool(POOL_FILL,
            TEXTURE_WIDTH,
            TEXTURE_HEIGHT,
            false);

    public TextureBucket(byte type) {
        super(type, false, true);
    }

    /**
     * holds textures and offset in vbo
     */
    public TextureItem textures;

    /**
     * scale mode
     */
    public boolean fixed;

    @Override
    protected void compile(ShortBuffer vboData, ShortBuffer iboData) {

        for (TextureItem t = textures; t != null; t = t.next)
            t.upload();

        /* add vertices to vbo */
        compileVertexItems(vboData);
    }

    @Override
    protected void clear() {
        while (textures != null)
            textures = textures.dispose();
        super.clear();
    }

    static class Shader extends GLShader {
        int uMV, uProj, uScale, uCoordScale, uTexSize, aPos, aTexCoord;

        Shader() {
            if (!create("texture_layer"))
                return;

            uMV = getUniform("u_mv");
            uProj = getUniform("u_proj");
            uScale = getUniform("u_scale");
            uCoordScale = getUniform("u_coord_scale");
            uTexSize = getUniform("u_div");
            aPos = getAttrib("a_pos");
            aTexCoord = getAttrib("a_tex_coord");
        }

        @Override
        public boolean useProgram() {
            if (super.useProgram()) {
                GLState.enableVertexArrays(aPos, aTexCoord);
                return true;
            }
            return false;
        }
    }

    static Shader shader;

    public static final class Renderer {

        static void init() {
            shader = new Shader();

            /* FIXME pool should be disposed on exit... */
            pool.init(0);
        }

        public static RenderBucket draw(RenderBucket b, GLViewport v, float scale) {

            GLState.test(false, false);
            GLState.blend(true);

            shader.useProgram();

            TextureBucket tb = (TextureBucket) b;
            gl.uniform1f(shader.uScale, tb.fixed ? 1 / scale : 1);
            gl.uniform1f(shader.uCoordScale, COORD_SCALE);

            v.proj.setAsUniform(shader.uProj);
            v.mvp.setAsUniform(shader.uMV);

            MapRenderer.bindQuadIndicesVBO();

            for (TextureItem t = tb.textures; t != null; t = t.next) {
                gl.uniform2f(shader.uTexSize,
                        1f / (t.width * COORD_SCALE),
                        1f / (t.height * COORD_SCALE));
                t.bind();

                /* draw up to maxVertices in each iteration */
                for (int i = 0; i < t.indices; i += MAX_INDICES) {
                    /* to.offset * (24(shorts) * 2(short-bytes)
                     * / 6(indices) == 8) */
                    int off = (t.offset + i) * RenderBuckets.SHORT_BYTES * 4 + tb.vertexOffset;

                    int numIndices = t.indices - i;
                    if (numIndices > MAX_INDICES)
                        numIndices = MAX_INDICES;

                    tb.render(off, numIndices);
                }
            }

            return b.next;
        }
    }

    public TextureItem getTextures() {
        return textures;
    }

    public void render(int offset, int numIndices) {
        gl.vertexAttribPointer(shader.aPos, 4, GL.SHORT,
                false, RenderBuckets.SHORT_BYTES * 6, offset);

        gl.vertexAttribPointer(shader.aTexCoord, 2, GL.SHORT,
                false, RenderBuckets.SHORT_BYTES * 6, offset + RenderBuckets.SHORT_BYTES * 4);

        gl.drawElements(GL.TRIANGLES, numIndices,
                GL.UNSIGNED_SHORT, 0);
    }
}
