using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VisimBox.Renderer;
using VisimBox.Resources;
using OpenTK.Graphics.OpenGL;
using System.Runtime.InteropServices;

namespace VisimRenderGL
{
    class GLIndexBuffer : IndexBuffer
    {


        public GLIndexBuffer(int count, bool cache = false, ResourceUsage usage = ResourceUsage.Static)
            : base(count, cache , usage)
        {
        }

        public GLIndexBuffer(int[] indices, bool cache = false, ResourceUsage usage = ResourceUsage.Static)
            : base(indices, false, usage)
        {
        }

        protected override void ConstructBuffer(int count, bool justPrepare = false)
        {
            if (_internalID != 0)
                ReleaseBuffer();
            uint rval = 0;
            GL.GenBuffers(1, out rval);
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, rval);
            if (rval != 0)
            {
                _internalID = rval;
            }
            else throw new InvalidOperationException("Can't construct opengl index buffer");
            _count = count;
            if (justPrepare)
                return;

            int size = count;

            GL.BufferData(BufferTarget.ElementArrayBuffer, (IntPtr)size, IntPtr.Zero, GLVertexBuffer.GetHint(this._usage));
        }

        public override uint InternalID
        {
            get { return _internalID; }
        }

        public override void BindBuffer()
        {
            if (_internalID == 0)
                return;
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, _internalID);
        }

        public override void WriteCache()
        {
            if (!_cached)
                return;
            if (_cache == null)
                return;

            BindBuffer();

            GCHandle gch = GCHandle.Alloc(this._cache, GCHandleType.Pinned);
            IntPtr data = gch.AddrOfPinnedObject();

            int size = Marshal.SizeOf(_cache[0]) * _cache.Length;
            int testsize;

            GL.BufferData(BufferTarget.ElementArrayBuffer, (IntPtr)size, data, GLVertexBuffer.GetHint(this._usage));

            gch.Free();

            GL.GetBufferParameter(BufferTarget.ElementArrayBuffer, BufferParameterName.BufferSize, out testsize);
            if (size != testsize)
            {
                ReleaseBuffer();
                throw new ApplicationException("IndexBuffer: Error when uploading index buffer!");
            }
        }

        public override void ReadCache(int start, int count)
        {
            if ((!_cached) || (_cache == null))
                return;
            if ((start < 0) || ((start + count) > _cache.Length))
                throw new ArgumentException("Wrong start/count for reading cache!");


            BindBuffer();

            int length = count - start;

            int offset = Marshal.SizeOf(_cache[0]) * start;


            int size = Marshal.SizeOf(_cache[0]) * length;
            GCHandle gch = GCHandle.Alloc(this._cache, GCHandleType.Pinned);
            IntPtr data = gch.AddrOfPinnedObject();

            GL.GetBufferSubData(BufferTarget.ElementArrayBuffer, (IntPtr)offset, (IntPtr)size, data);
            gch.Free();
        }

        public override void WriteCache(int start, int count)
        {
            BindBuffer();
            if ((!_cached) || (_cache == null))
                return;
            if ((start < 0) || ((start + count) > _cache.Length))
                throw new ArgumentException("Wrong start/count for reading cache!");


            BindBuffer();

            int length = count - start;

            int offset = Marshal.SizeOf(_cache[0]) * start;


            int size = Marshal.SizeOf(_cache[0]) * length;
            GCHandle gch = GCHandle.Alloc(this._cache, GCHandleType.Pinned);
            IntPtr data = gch.AddrOfPinnedObject();

            GL.BufferSubData(BufferTarget.ElementArrayBuffer, (IntPtr)offset, (IntPtr)size, data);
            gch.Free();
        }


        public override int ReadElement(int position)
        {
            if ((position > _count) || (position < 0))
                throw new ArgumentException("ReadElement called with wrong position parameter");
            BindBuffer();

            int rval = 0;
            int size = Marshal.SizeOf(rval);
            int offset = size * position;

            GCHandle gch = GCHandle.Alloc(rval, GCHandleType.Pinned);
            IntPtr data = gch.AddrOfPinnedObject();

            GL.GetBufferSubData(BufferTarget.ElementArrayBuffer, (IntPtr)offset, (IntPtr)size, data);
            gch.Free();
            return rval;
        }

        public override void WriteElement(int position, ref int value)
        {
            if ((position > _count) || (position < 0))
                throw new ArgumentException("WriteElement called with wrong position parameter");
            BindBuffer();

            int size = Marshal.SizeOf(value);
            int offset = size * position;

            GCHandle gch = GCHandle.Alloc(value, GCHandleType.Pinned);
            IntPtr data = gch.AddrOfPinnedObject();

            GL.BufferSubData(BufferTarget.ElementArrayBuffer, (IntPtr)offset, (IntPtr)size, data);
            gch.Free();
        }

        public override string FileName
        {
            get
            {
                return null;
            }
            set
            {
            }
        }

        protected void ReleaseBuffer()
        {
            if (_internalID != 0)
            {
                GL.DeleteBuffers(1, ref _internalID);
                _internalID = 0;
            }            
        }

        public override void Dispose()
        {
            ReleaseBuffer();
            base.Dispose();
        }
    }
}
