#include "main/mtypes.h"
#include "main/framebuffer.h"
#include "main/renderbuffer.h"
#include "main/imports.h"
#include "drirenderbuffer.h"


/**
 * This will get called when a window (gl_framebuffer) is resized (probably
 * via driUpdateFramebufferSize(), below).
 * Just update width, height and internal format fields for now.
 * There's usually no memory allocation above because the present
 * DRI drivers use statically-allocated full-screen buffers. If that's not
 * the case for a DRI driver, a different AllocStorage method should
 * be used.
 */
static GLboolean
driRenderbufferStorage(GLcontext *ctx, struct gl_renderbuffer *rb,
                       GLenum internalFormat, GLuint width, GLuint height)
{
   rb->Width = width;
   rb->Height = height;
   rb->InternalFormat = internalFormat;
   return GL_TRUE;
}


static void
driDeleteRenderbuffer(struct gl_renderbuffer *rb)
{
   /* don't free rb->Data  Chances are it's a memory mapped region for
    * the dri drivers.
    */
   _mesa_free(rb);
}


/**
 * Allocate a new driRenderbuffer object.
 * Individual drivers are free to implement different versions of
 * this function.
 *
 * At this time, this function can only be used for window-system
 * renderbuffers, not user-created RBOs.
 *
 * \param format  Either GL_RGBA, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24,
 *                GL_DEPTH_COMPONENT32, or GL_STENCIL_INDEX8_EXT (for now).
 * \param addr  address in main memory of the buffer.  Probably a memory
 *              mapped region.
 * \param cpp  chars or bytes per pixel
 * \param offset  start of renderbuffer with respect to start of framebuffer
 * \param pitch   pixels per row
 */
driRenderbuffer *
driNewRenderbuffer(GLenum format, GLvoid *addr,
                   GLint cpp, GLint offset, GLint pitch,
                   __DRIdrawablePrivate *dPriv)
{
   driRenderbuffer *drb;

   assert(format == GL_RGBA ||
          format == GL_RGB5 ||
          format == GL_RGBA8 ||
          format == GL_DEPTH_COMPONENT16 ||
          format == GL_DEPTH_COMPONENT24 ||
          format == GL_DEPTH_COMPONENT32 ||
          format == GL_STENCIL_INDEX8_EXT);

   assert(cpp > 0);
   assert(pitch > 0);

   drb = _mesa_calloc(sizeof(driRenderbuffer));
   if (drb) {
      const GLuint name = 0;

      _mesa_init_renderbuffer(&drb->Base, name);

      /* Make sure we're using a null-valued GetPointer routine */
      assert(drb->Base.GetPointer(NULL, &drb->Base, 0, 0) == NULL);

      drb->Base.InternalFormat = format;

      if (format == GL_RGBA || format == GL_RGB5 || format == GL_RGBA8) {
         /* Color */
         drb->Base._BaseFormat = GL_RGBA;
         drb->Base.DataType = GL_UNSIGNED_BYTE;
         if (format == GL_RGB5) {
            drb->Base.RedBits = 5;
            drb->Base.GreenBits = 6;
            drb->Base.BlueBits = 5;
         }
         else {
            drb->Base.RedBits =
            drb->Base.GreenBits =
            drb->Base.BlueBits =
            drb->Base.AlphaBits = 8;
         }
      }
      else if (format == GL_DEPTH_COMPONENT16) {
         /* Depth */
         drb->Base._BaseFormat = GL_DEPTH_COMPONENT;
         /* we always Get/Put 32-bit Z values */
         drb->Base.DataType = GL_UNSIGNED_INT;
         drb->Base.DepthBits = 16;
      }
      else if (format == GL_DEPTH_COMPONENT24) {
         /* Depth */
         drb->Base._BaseFormat = GL_DEPTH_COMPONENT;
         /* we always Get/Put 32-bit Z values */
         drb->Base.DataType = GL_UNSIGNED_INT;
         drb->Base.DepthBits = 24;
      }
      else if (format == GL_DEPTH_COMPONENT32) {
         /* Depth */
         drb->Base._BaseFormat = GL_DEPTH_COMPONENT;
         /* we always Get/Put 32-bit Z values */
         drb->Base.DataType = GL_UNSIGNED_INT;
         drb->Base.DepthBits = 32;
      }
      else {
         /* Stencil */
         ASSERT(format == GL_STENCIL_INDEX8_EXT);
         drb->Base._BaseFormat = GL_STENCIL_INDEX;
         drb->Base.DataType = GL_UNSIGNED_BYTE;
         drb->Base.StencilBits = 8;
      }

      /* XXX if we were allocating a user-created renderbuffer, we'd have
       * to fill in the Red/Green/Blue/.../Bits values too.
       */

      drb->Base.AllocStorage = driRenderbufferStorage;
      drb->Base.Delete = driDeleteRenderbuffer;

      drb->Base.Data = addr;

      /* DRI renderbuffer-specific fields: */
      drb->dPriv = dPriv;
      drb->offset = offset;
      drb->pitch = pitch;
      drb->cpp = cpp;

      /* may be changed if page flipping is active: */
      drb->flippedOffset = offset;
      drb->flippedPitch = pitch;
      drb->flippedData = addr;
   }
   return drb;
}


/**
 * Update the front and back renderbuffers' flippedPitch/Offset/Data fields.
 * If stereo, flip both the left and right pairs.
 * This is used when we do double buffering via page flipping.
 * \param fb  the framebuffer we're page flipping
 * \param flipped  if true, set flipped values, else set non-flipped values
 */
void
driFlipRenderbuffers(struct gl_framebuffer *fb, GLboolean flipped)
{
   const GLuint count = fb->Visual.stereoMode ? 2 : 1;
   GLuint lr; /* left or right */

   /* we shouldn't really call this function if single-buffered, but
    * play it safe.
    */
   if (!fb->Visual.doubleBufferMode)
      return;

   for (lr = 0; lr < count; lr++) {
      GLuint frontBuf = (lr == 0) ? BUFFER_FRONT_LEFT : BUFFER_FRONT_RIGHT;
      GLuint backBuf  = (lr == 0) ? BUFFER_BACK_LEFT  : BUFFER_BACK_RIGHT;
      driRenderbuffer *front_drb
         = (driRenderbuffer *) fb->Attachment[frontBuf].Renderbuffer;
      driRenderbuffer *back_drb
         = (driRenderbuffer *) fb->Attachment[backBuf].Renderbuffer;

      if (flipped) {
         front_drb->flippedOffset = back_drb->offset;
         front_drb->flippedPitch  = back_drb->pitch;
         front_drb->flippedData   = back_drb->Base.Data;
         back_drb->flippedOffset  = front_drb->offset;
         back_drb->flippedPitch   = front_drb->pitch;
         back_drb->flippedData    = front_drb->Base.Data;
      }
      else {
         front_drb->flippedOffset = front_drb->offset;
         front_drb->flippedPitch  = front_drb->pitch;
         front_drb->flippedData   = front_drb->Base.Data;
         back_drb->flippedOffset  = back_drb->offset;
         back_drb->flippedPitch   = back_drb->pitch;
         back_drb->flippedData    = back_drb->Base.Data;
      }
   }
}


/**
 * Check that the gl_framebuffer associated with dPriv is the right size.
 * Resize the gl_framebuffer if needed.
 * It's expected that the dPriv->driverPrivate member points to a
 * gl_framebuffer object.
 */
void
driUpdateFramebufferSize(GLcontext *ctx, const __DRIdrawablePrivate *dPriv)
{
   struct gl_framebuffer *fb = (struct gl_framebuffer *) dPriv->driverPrivate;
   if (fb && (dPriv->w != fb->Width || dPriv->h != fb->Height)) {
      ctx->Driver.ResizeBuffers(ctx, fb, dPriv->w, dPriv->h);
      /* if the driver needs the hw lock for ResizeBuffers, the drawable
         might have changed again by now */
      assert(fb->Width == dPriv->w);
      assert(fb->Height == dPriv->h);
   }
}