/**
 * PANDA 3D SOFTWARE
 * Copyright (c) Carnegie Mellon University.  All rights reserved.
 *
 * All use of this software is subject to the terms of the revised BSD
 * license.  You should have received a copy of this license along
 * with this source code in a file named "LICENSE."
 *
 * @file cocoaGLGraphicsBuffer.mm
 * @author rdb
 * @date 2017-12-19
 */

#include "cocoaGLGraphicsBuffer.h"
#include "cocoaGLGraphicsStateGuardian.h"
#include "config_cocoadisplay.h"
#include "cocoaGraphicsPipe.h"

#import <OpenGL/OpenGL.h>

TypeHandle CocoaGLGraphicsBuffer::_type_handle;

/**
 *
 */
CocoaGLGraphicsBuffer::
CocoaGLGraphicsBuffer(GraphicsEngine *engine, GraphicsPipe *pipe,
                    const std::string &name,
                    const FrameBufferProperties &fb_prop,
                    const WindowProperties &win_prop,
                    int flags,
                    GraphicsStateGuardian *gsg,
                    GraphicsOutput *host) : // Ignore the host.
  GLGraphicsBuffer(engine, pipe, name, fb_prop, win_prop, flags, gsg, nullptr)
{
}

/**
 * This function will be called within the draw thread before beginning
 * rendering for a given frame.  It should do whatever setup is required, and
 * return true if the frame should be rendered, or false if it should be
 * skipped.
 */
bool CocoaGLGraphicsBuffer::
begin_frame(FrameMode mode, Thread *current_thread) {
  if (_gsg == nullptr) {
    return false;
  }

  CocoaGLGraphicsStateGuardian *cocoagsg;
  DCAST_INTO_R(cocoagsg, _gsg, false);
  nassertr(cocoagsg->_context != nil, false);

  // Lock the context and make it current.
  {
    PStatTimer timer(_make_current_pcollector, current_thread);
    cocoagsg->lock_context();
    [cocoagsg->_context makeCurrentContext];
  }

  return GLGraphicsBuffer::begin_frame(mode, current_thread);
}

/**
 * This function will be called within the draw thread after rendering is
 * completed for a given frame.  It should do whatever finalization is
 * required.
 */
void CocoaGLGraphicsBuffer::
end_frame(FrameMode mode, Thread *current_thread) {
  nassertv(_gsg != nullptr);

  GLGraphicsBuffer::end_frame(mode, current_thread);

  // Release the context.
  CocoaGLGraphicsStateGuardian *cocoagsg;
  DCAST_INTO_V(cocoagsg, _gsg);
  cocoagsg->unlock_context();
}

/**
 * Opens the buffer right now.  Called from the window thread.  Returns true
 * if the buffer is successfully opened, or false if there was a problem.
 */
bool CocoaGLGraphicsBuffer::
open_buffer() {
  CocoaGraphicsPipe *cocoa_pipe;
  DCAST_INTO_R(cocoa_pipe, _pipe, false);

  // GSG CreationInitialization
  CocoaGLGraphicsStateGuardian *cocoagsg;
  if (_gsg == nullptr) {
    // There is no old gsg.  Create a new one.
    cocoagsg = new CocoaGLGraphicsStateGuardian(_engine, _pipe, nullptr);
    cocoagsg->choose_pixel_format(_fb_properties, cocoa_pipe->get_display_id(), false);
    _gsg = cocoagsg;
  } else {
    // If the old gsg has the wrong pixel format, create a new one that shares
    // with the old gsg.
    DCAST_INTO_R(cocoagsg, _gsg, false);
    if (!cocoagsg->get_fb_properties().subsumes(_fb_properties)) {
      cocoagsg = new CocoaGLGraphicsStateGuardian(_engine, _pipe, cocoagsg);
      cocoagsg->choose_pixel_format(_fb_properties, cocoa_pipe->get_display_id(), false);
      _gsg = cocoagsg;
    }
  }

  if (cocoagsg->_context == nil) {
    // Could not obtain a proper context.
    _gsg.clear();
    return false;
  }

  FrameBufferProperties desired_props(_fb_properties);

  // Lock the context, so we can safely operate on it.
  cocoagsg->lock_context();

  // Make the context current and initialize what we need.
  [cocoagsg->_context makeCurrentContext];
  [cocoagsg->_context update];
  cocoagsg->reset_if_new();

  // These properties are determined by choose_pixel_format.
  _fb_properties.set_force_hardware(cocoagsg->_fbprops.get_force_hardware());
  _fb_properties.set_force_software(cocoagsg->_fbprops.get_force_software());

  bool success = GLGraphicsBuffer::open_buffer();
  if (success) {
    rebuild_bitplanes();
    if (_needs_rebuild) {
      // If it still needs rebuild, then something must have gone wrong.
      success = false;
    }
  }

  if (success && !_fb_properties.verify_hardware_software
      (desired_props, cocoagsg->get_gl_renderer())) {
    GLGraphicsBuffer::close_buffer();
    success = false;
  }

  // Release the context.
  cocoagsg->unlock_context();

  if (!success) {
    return false;
  }

  return true;
}

/**
 * Closes the buffer right now.  Called from the window thread.
 */
void CocoaGLGraphicsBuffer::
close_buffer() {
  if (_gsg != nullptr) {
    CocoaGLGraphicsStateGuardian *cocoagsg;
    cocoagsg = DCAST(CocoaGLGraphicsStateGuardian, _gsg);

    if (cocoagsg != nullptr && cocoagsg->_context != nil) {
      cocoagsg->lock_context();
      GLGraphicsBuffer::close_buffer();
      cocoagsg->unlock_context();
    }
    _gsg.clear();
  } else {
    GLGraphicsBuffer::close_buffer();
  }
}
