using System;
using System.Text;
using SlatedGameToolkit.Framework.Exceptions;
using SlatedGameToolkit.Framework.Graphics.OpenGL;
using SlatedGameToolkit.Framework.Graphics.Render.Shaders;
using SlatedGameToolkit.Framework.Graphics.Window;

namespace SlatedGameToolkit.Framework.Graphics.Render.Programs
{
    public class RenderProgram : IDisposable
    {
        public GLContext GLContext {get; private set; }
        private bool disposed;

        public uint Handle { get; private set; }
        public RenderProgram(GLContext context = null, params IShadeable[] shaders) {
            this.GLContext = context ?? WindowContextsManager.CurrentGL;
            Handle = GLContext.CreateProgram();
            foreach (IShadeable shader in shaders)
            {
                if (this.GLContext != shader.GLContext) throw new FrameworkUsageException("OpenGL contexts between attached shaders and program must match.");
                GLContext.AttachShader(Handle, shader.Handle);
            }
            GLContext.LinkProgram(Handle);
            int status;
            GLContext.GetProgramiv(Handle, ProgramPropertyARB.LinkStatus, out status);
            if ((OpenGL.Boolean)status == OpenGL.Boolean.False) {
                int logLength;
                GLContext.GetProgramiv(Handle, ProgramPropertyARB.InfoLogLength, out logLength);
                byte[] logData = new byte[logLength];
                int byteCount;
                GLContext.GetProgramInfoLog(Handle, logLength, out byteCount, logData);
                throw new FrameworkUsageException(Encoding.UTF8.GetString(logData));
            }

            foreach (IShadeable shader in shaders)
            {
                shader.Dispose();
            }
        }

        public void Use() {
            GLContext.UseProgram(Handle);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                }

                GLContext.DeleteProgram(Handle);
                disposed = true;
            }
        }

        ~RenderProgram()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }
    }
}