/************************************************************************************

 Authors     :   Bradley Austin Davis <bdavis@saintandreas.org>
 Copyright   :   Copyright Brad Davis. All Rights reserved.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.

 ************************************************************************************/

#pragma once

#ifndef GL_ZERO
#error "You must include the gl headers before including this file"
#endif

#include "GlTexture.h"
#include "GlDebug.h"

namespace gl {

struct FrameBuffer {
  GLuint frameBuffer;

  FrameBuffer() : frameBuffer(0) {
    glGenFramebuffers(1, &frameBuffer);
  }

  virtual ~FrameBuffer() {
    if (frameBuffer) {
      glDeleteFramebuffers(1, &frameBuffer);
      GL_CHECK_ERROR;
    }
  }

  void bind(GLenum target = GL_FRAMEBUFFER) {
    glBindFramebuffer(target, frameBuffer);
  }

  static void unbind(GLenum target = GL_FRAMEBUFFER) {
    glBindFramebuffer(target, 0);
  }

  static bool checkStatus(GLenum target = GL_FRAMEBUFFER) {
      GLuint status = glCheckFramebufferStatus(target);
      switch(status) {
      case GL_FRAMEBUFFER_COMPLETE:
          return true;
          break;

      case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
          std::cerr << "framebuffer incomplete attachment" << std::endl;
          break;

      case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
          std::cerr << "framebuffer missing attachment" << std::endl;
          break;

      case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
          std::cerr << "framebuffer incomplete draw buffer" << std::endl;
          break;

      case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
          std::cerr << "framebuffer incomplete read buffer" << std::endl;
          break;

      case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
          std::cerr << "framebuffer incomplete multisample" << std::endl;
          break;

      case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS :
          std::cerr << "framebuffer incomplete layer targets" << std::endl;
          break;

      case GL_FRAMEBUFFER_UNSUPPORTED:
          std::cerr << "framebuffer unsupported internal format or image" << std::endl;
          break;

      default:
          std::cerr << "other framebuffer error" << std::endl;
          break;
      }

      return false;
  }

  template <GLenum TextureType, GLenum TextureFormat>
  void attach(int attachPoint, std::shared_ptr<Texture<TextureType, TextureFormat> > & texture) {
    glFramebufferTexture2D(GL_FRAMEBUFFER, attachPoint, TextureType, *texture, 0);
  }
};

typedef std::shared_ptr<FrameBuffer> FrameBufferPtr;

template <GLenum ColorFormat = GL_RGBA8>
struct TFrameBufferWrapper {
  typedef Texture<GL_TEXTURE_2D, ColorFormat> ColorTexture;
  FrameBufferPtr frameBuffer;
  typename ColorTexture::Ptr color;
  Texture2dDepth::Ptr depth;
  glm::ivec2 size;

  void init(const glm::ivec2 & size) {
    this->size = size;
    GL_CHECK_ERROR;
    if (!frameBuffer) {
      frameBuffer = FrameBufferPtr(new FrameBuffer());
    }
    frameBuffer->bind();
    GL_CHECK_ERROR;

    if (!color) {
      color = typename ColorTexture::Ptr(new ColorTexture());
      color->bind();
      color->parameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
      color->parameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
      color->parameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
      color->parameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
      color->storage2d(size);
      Texture2d::unbind();
    }
    frameBuffer->attach(GL_COLOR_ATTACHMENT0, color);
    GL_CHECK_ERROR;

    if (!depth) {
      depth = Texture2dDepth::Ptr(new Texture2dDepth());
      depth->bind();
      depth->storage2d(size);
      Texture2dDepth::unbind();
    }
    frameBuffer->attach(GL_DEPTH_ATTACHMENT, depth);
    GL_CHECK_ERROR;


    if (!frameBuffer->checkStatus()) {
      throw std::runtime_error("Bad framebuffer creation");
    }
    GL_CHECK_ERROR;
    FrameBuffer::unbind();
    GL_CHECK_ERROR;
  }

  void activate() {
    frameBuffer->bind();
    viewport(size);
  }

  static void deactivate() {
    FrameBuffer::unbind();
  }

};

typedef TFrameBufferWrapper<> FrameBufferWrapper;

} // gl


