/**
 *
 */
package jp.sourceforge.nicoro;

import static jp.sourceforge.nicoro.Log.LOG_TAG;

import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicInteger;

import jp.gr.java_conf.shiseissi.commonlib.CacheReference;

public abstract class VideoDrawBuffer<T> {
    private static final boolean DEBUG_LOGD = Release.IS_DEBUG & true;
    private static final boolean DEBUG_LOGV = Release.IS_DEBUG & true;

    public int frame;
    public long when;

    public abstract T strengthenBuffer();
    public abstract void weakenBuffer();
    // TODO
//	public abstract void resetBuffer(T buf);
    public abstract void resetBuffer(Object buf);
	public abstract boolean hasBufferRef();
	public abstract boolean isStrengthBuffer();
    public abstract T createDrawBuffer(int width, int height);

    public static class IntArray32bit extends VideoDrawBuffer<int[]> {
        private static final Runtime mRuntime = Runtime.getRuntime();
        private static final long mMaxMemory = mRuntime.maxMemory();
//        private static final long mFreeMemoryMarginSize = 6 * 1024 * 1024;
//        private static final long mFreeMemoryMarginSize = mMaxMemory / 2;
        private static final long mFreeMemoryMarginSize = mMaxMemory * 2 / 3;

        private CacheReference<int[]> mBuffer;

        @Override
        public int[] strengthenBuffer() {
            if (mBuffer == null) {
                return null;
            } else {
                return mBuffer.strengthen();
            }
        }
        @Override
        public void weakenBuffer() {
            if (mBuffer != null) {
                mBuffer.weaken();
            }
        }
        // TODO
        @Override
    //  public void resetBuffer(int[] buf) {
        public void resetBuffer(Object buf) {
            if (mBuffer == null) {
//                mBuffer = new CacheReference<int[]>(buf);
                mBuffer = new CacheReference<int[]>((int[]) buf);
            } else {
//                mBuffer.reset(buf);
                mBuffer.reset((int[]) buf);
            }
        }
        @Override
        public boolean hasBufferRef() {
            return mBuffer != null;
        }
        @Override
        public boolean isStrengthBuffer() {
            if (mBuffer == null) {
                return false;
            } else {
                return mBuffer.isStrength();
            }
        }

        @Override
        public int[] createDrawBuffer(int width, int height) {
            final int area = width * height;
            int[] buf;
            if (hasEnoughFreeMemory(area * 4)) {
                buf = new int[area];
            } else {
                buf = null;
            }
            resetBuffer(buf);
            return buf;
        }

        private boolean hasEnoughFreeMemory(long size) {
            final Runtime runtime = mRuntime;
            final long freeSize = mMaxMemory - runtime.totalMemory() + runtime.freeMemory();
            final boolean ret = (freeSize > size + mFreeMemoryMarginSize);
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, Log.buf().append("hasEnoughFreeMemory() maxMemory=")
                        .append(mMaxMemory).append(" freeSize=")
                        .append(freeSize).append(" return=")
                        .append(ret)
                        .toString());
            }
            return ret;
        }
    }

    public static class ByteBuffer16bit extends VideoDrawBuffer<ByteBuffer> {
        private static AtomicInteger sAllocateSize = new AtomicInteger();
        private static int sAllocateMaxSize;

        private ByteBuffer mBuffer;
        private int mSize = 0;

        @Override
        public ByteBuffer strengthenBuffer() {
            return mBuffer;
        }
        @Override
        public void weakenBuffer() {
            // 何もしない
        }
        // TODO
        @Override
    //  public void resetBuffer(int[] buf) {
        public void resetBuffer(Object buf) {
            mBuffer = (ByteBuffer) buf;
        }
        @Override
        public boolean hasBufferRef() {
            return mBuffer != null;
        }
        @Override
        public boolean isStrengthBuffer() {
            return mBuffer != null;
        }

        @Override
        public ByteBuffer createDrawBuffer(int width, int height) {
            assert sAllocateMaxSize > 0;
            final int size = width * height * 2;
            ByteBuffer buf;
            if (sAllocateSize.get() + size > sAllocateMaxSize) {
                buf = null;
                mSize = 0;
            } else {
                buf = ByteBuffer.allocateDirect(size);
                mSize = size;
                int currentSize = sAllocateSize.addAndGet(size);
                if (DEBUG_LOGD) {
                    Log.d(LOG_TAG, Log.buf().append("ByteBuffer16bit#createDrawBuffer() sAllocateSize=")
                            .append(currentSize).toString());
                }
            }
            resetBuffer(buf);
            return buf;
        }

        @Override
        protected void finalize() throws Throwable {
            try {
                if (DEBUG_LOGV) {
                    Log.v(LOG_TAG, "ByteBuffer16bit#finalize start");
                }
                super.finalize();
            } finally {
                int currentSize = sAllocateSize.addAndGet(-mSize);
                if (DEBUG_LOGD) {
                    Log.d(LOG_TAG, Log.buf().append("ByteBuffer16bit#finalize() sAllocateSize=")
                            .append(currentSize).toString());
                }
                if (DEBUG_LOGV) {
                    Log.v(LOG_TAG, "ByteBuffer16bit#finalize end");
                }
            }
        }

        public static void setAllocateMaxSizeMB(int sizeMB) {
            sAllocateMaxSize = sizeMB * 1024 * 1024;
        }
    }
}