package org.ebookdroid.common.bitmaps;

import org.ebookdroid.core.PagePaint;

import android.graphics.PointF;
import android.graphics.RectF;
import android.util.FloatMath;

import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.emdev.common.log.LogContext;
import org.emdev.common.log.LogManager;
import org.emdev.ui.gl.GLCanvas;
import org.emdev.utils.MathUtils;

public class GLBitmaps {

    protected static final LogContext LCTX = LogManager.root().lctx("Bitmaps", false);

    protected static final ThreadLocal<ByteBufferBitmap> threadSlices = new ThreadLocal<ByteBufferBitmap>();

    protected final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public final int width;
    public final int height;
    public final String nodeId;
    public final int partSize;
    public final int columns;
    public final int rows;

    private ByteBufferBitmap[] bitmaps;

    private ByteBufferTexture[] textures;

    public GLBitmaps(final String nodeId, final ByteBufferBitmap orig, final boolean invert) {
        this.nodeId = nodeId;
        this.width = orig.width;
        this.height = orig.height;
        this.partSize = BitmapManager.partSize;
        this.columns = getPartCount(width, partSize);
        this.rows = getPartCount(height, partSize);
        this.bitmaps = ByteBufferManager.getParts(partSize, rows, columns);

        if (invert) {
            orig.invert();
        }

        final int color = invert ? PagePaint.NIGHT.fillPaint.getColor() : PagePaint.DAY.fillPaint.getColor();

        int top = 0;
        for (int row = 0; row < rows; row++, top += partSize) {
            int left = 0;
            for (int col = 0; col < columns; col++, left += partSize) {
                final int index = row * columns + col;
                try {
                    if (row == rows - 1 || col == columns - 1) {
                        final int right = Math.min(left + partSize, orig.width);
                        final int bottom = Math.min(top + partSize, orig.height);
                        bitmaps[index].eraseColor(color);
                        bitmaps[index].copyPixelsFrom(orig, left, top, right - left, bottom - top);
                    } else {
                        bitmaps[index].copyPixelsFrom(orig, left, top, partSize, partSize);
                    }
                } catch (final IllegalArgumentException ex) {
                    LCTX.e("Cannot create part: " + row + "/" + rows + ", " + col + "/" + columns + ": "
                            + ex.getMessage());
                }
            }
        }
    }

    private static int getPartCount(final int size, final int partSize) {
        if (size % partSize == 0) {
            return size / partSize;
        }
        return (int) FloatMath.ceil(size / (float) partSize);
    }

    public boolean drawGL(final GLCanvas canvas, final PagePaint paint, final PointF vb, final RectF tr, final RectF cr) {
        lock.writeLock().lock();
        try {
            if (textures == null) {
                if (this.bitmaps == null) {
                    return false;
                }
                textures = new ByteBufferTexture[columns * rows];
                for (int i = 0; i < textures.length; i++) {
                    textures[i] = new ByteBufferTexture(bitmaps[i]);
                }
            }

            if (LCTX.isDebugEnabled()) {
                LCTX.d(nodeId + ".drawGL(): >>>>");
            }

            final RectF actual = new RectF(cr.left - vb.x, cr.top - vb.y, cr.right - vb.x, cr.bottom - vb.y);
            MathUtils.round(actual);
            canvas.setClipRect(actual);

            final boolean res = draw(canvas, vb, tr, actual);

            if (res && bitmaps != null) {
                ByteBufferManager.release(bitmaps);
                bitmaps = null;
            }

            if (LCTX.isDebugEnabled()) {
                LCTX.d(nodeId + ".drawGL(): <<<<<");
            }

            canvas.clearClipRect();

            return res;
        } finally {
            lock.writeLock().unlock();
        }
    }

    protected boolean draw(final GLCanvas canvas, final PointF vb, final RectF tr, final RectF actual) {
        final float offsetX = tr.left - vb.x;
        final float offsetY = tr.top - vb.y;

        final float scaleX = tr.width() / width;
        final float scaleY = tr.height() / height;

        final float sizeX = partSize * scaleX;
        final float sizeY = partSize * scaleY;

        final RectF src = new RectF();
        final RectF rect = new RectF(offsetX, offsetY, offsetX + sizeX, offsetY + sizeY);
        final RectF r = new RectF();

        boolean res = true;
        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < columns; col++) {
                final int index = row * columns + col;
                res &= draw(canvas, row, col, this.textures[index], actual, src, rect, r);
                rect.left += sizeX;
                rect.right += sizeX;
            }
            rect.left = offsetX;
            rect.right = offsetX + sizeX;

            rect.top += sizeY;
            rect.bottom += sizeY;
        }
        return res;
    }

    protected boolean draw(final GLCanvas canvas, final int row, final int col, final ByteBufferTexture t,
            final RectF actual, final RectF src, final RectF rect, final RectF r) {
        boolean tres = false;
        if (t != null) {
            r.set(rect);
            src.set(0, 0, t.getWidth(), t.getHeight());
            if (!(r.right < actual.left || r.left > actual.right || r.bottom < actual.top || r.top > actual.bottom)) {
                tres = canvas.drawTexture(t, src, r);
                if (LCTX.isDebugEnabled()) {
                    LCTX.d(nodeId + ": " + row + "." + col + " : " + t.getId() + " = " + tres);
                }
            }
        }
        return tres;
    }

    ByteBufferBitmap[] clear() {
        lock.writeLock().lock();
        try {
            if (textures != null) {
                for (int i = 0; i < textures.length; i++) {
                    textures[i].recycle();
                }
                textures = null;
            }

            final ByteBufferBitmap[] ref = this.bitmaps;
            this.bitmaps = null;
            return ref;
        } finally {
            lock.writeLock().unlock();
        }
    }

    public boolean hasBitmaps() {
        lock.readLock().lock();
        try {
            return textures != null || bitmaps != null;
        } finally {
            lock.readLock().unlock();
        }
    }

}
