/*
 * Copyright (C) 2024 Pascal Nowack
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#include "config.h"

#include "grd-rdp-frame.h"

#include "grd-encode-context.h"
#include "grd-rdp-render-context.h"
#include "grd-rdp-renderer.h"

struct _GrdRdpFrame
{
  GrdRdpRenderer *renderer;
  GrdRdpRenderContext *render_context;

  GrdRdpFrameCallback frame_picked_up;
  GrdRdpFrameCallback view_finalized;
  GrdRdpFrameCallback frame_submitted;
  GrdRdpFrameCallback frame_finalized;
  gpointer callback_user_data;
  GDestroyNotify user_data_destroy;

  gboolean pending_view_finalization;

  GrdEncodeContext *encode_context;

  GList *acquired_image_views;
  GQueue *unused_image_views;

  GrdRdpBuffer *src_buffer_new;
  GrdRdpBuffer *src_buffer_old;

  GrdRdpFrameViewType view_type;

  cairo_region_t *damage_region;
  GList *bitstreams;
};

GrdRdpRenderer *
grd_rdp_frame_get_renderer (GrdRdpFrame *rdp_frame)
{
  return rdp_frame->renderer;
}

GrdRdpRenderContext *
grd_rdp_frame_get_render_context (GrdRdpFrame *rdp_frame)
{
  return rdp_frame->render_context;
}

GrdEncodeContext *
grd_rdp_frame_get_encode_context (GrdRdpFrame *rdp_frame)
{
  return rdp_frame->encode_context;
}

GList *
grd_rdp_frame_get_image_views (GrdRdpFrame *rdp_frame)
{
  return rdp_frame->acquired_image_views;
}

GrdRdpBuffer *
grd_rdp_frame_get_source_buffer (GrdRdpFrame *rdp_frame)
{
  return rdp_frame->src_buffer_new;
}

GrdRdpBuffer *
grd_rdp_frame_get_last_source_buffer (GrdRdpFrame *rdp_frame)
{
  return rdp_frame->src_buffer_old;
}

GrdRdpFrameViewType
grd_rdp_frame_get_avc_view_type (GrdRdpFrame *rdp_frame)
{
  return rdp_frame->view_type;
}

cairo_region_t *
grd_rdp_frame_get_damage_region (GrdRdpFrame *rdp_frame)
{
  return rdp_frame->damage_region;
}

GList *
grd_rdp_frame_get_bitstreams (GrdRdpFrame *rdp_frame)
{
  return rdp_frame->bitstreams;
}

gboolean
grd_rdp_frame_has_valid_view (GrdRdpFrame *rdp_frame)
{
  return !!rdp_frame->damage_region;
}

gboolean
grd_rdp_frame_is_surface_damaged (GrdRdpFrame *rdp_frame)
{
  return cairo_region_num_rectangles (rdp_frame->damage_region) > 0;
}

void
grd_rdp_frame_set_renderer (GrdRdpFrame    *rdp_frame,
                            GrdRdpRenderer *renderer)
{
  rdp_frame->renderer = renderer;
}

void
grd_rdp_frame_set_avc_view_type (GrdRdpFrame         *rdp_frame,
                                 GrdRdpFrameViewType  view_type)
{
  g_assert (view_type != GRD_RDP_FRAME_VIEW_TYPE_DUAL);
  g_assert (view_type == GRD_RDP_FRAME_VIEW_TYPE_MAIN ||
            view_type == GRD_RDP_FRAME_VIEW_TYPE_AUX);

  switch (rdp_frame->view_type)
    {
    case GRD_RDP_FRAME_VIEW_TYPE_DUAL:
      g_assert (g_queue_get_length (rdp_frame->unused_image_views) == 2);
      g_queue_pop_tail (rdp_frame->unused_image_views);
      break;
    case GRD_RDP_FRAME_VIEW_TYPE_MAIN:
    case GRD_RDP_FRAME_VIEW_TYPE_AUX:
      g_assert (g_queue_get_length (rdp_frame->unused_image_views) == 1);
      break;
    }

  rdp_frame->view_type = view_type;
}

static void
finalize_view (GrdRdpFrame *rdp_frame)
{
  g_assert (rdp_frame->pending_view_finalization);

  rdp_frame->view_finalized (rdp_frame, rdp_frame->callback_user_data);
  rdp_frame->pending_view_finalization = FALSE;
}

void
grd_rdp_frame_set_damage_region (GrdRdpFrame    *rdp_frame,
                                 cairo_region_t *damage_region)
{
  GrdRdpRenderContext *render_context = rdp_frame->render_context;

  g_assert (!rdp_frame->damage_region);

  rdp_frame->damage_region = damage_region;
  grd_encode_context_set_damage_region (rdp_frame->encode_context,
                                        damage_region);

  if (!grd_rdp_render_context_must_delay_view_finalization (render_context))
    finalize_view (rdp_frame);
}

void
grd_rdp_frame_set_bitstreams (GrdRdpFrame *rdp_frame,
                              GList       *bitstreams)
{
  rdp_frame->bitstreams = bitstreams;
}

void
grd_rdp_frame_notify_picked_up (GrdRdpFrame *rdp_frame)
{
  rdp_frame->frame_picked_up (rdp_frame, rdp_frame->callback_user_data);
}

void
grd_rdp_frame_notify_frame_submission (GrdRdpFrame *rdp_frame)
{
  rdp_frame->frame_submitted (rdp_frame, rdp_frame->callback_user_data);
}

GrdImageView *
grd_rdp_frame_pop_image_view (GrdRdpFrame *rdp_frame)
{
  return g_queue_pop_head (rdp_frame->unused_image_views);
}

static void
set_view_type (GrdRdpFrame *rdp_frame,
               gboolean     frame_upgrade)
{
  GrdRdpRenderContext *render_context = rdp_frame->render_context;
  GrdRdpCodec codec = grd_rdp_render_context_get_codec (render_context);

  switch (codec)
    {
    case GRD_RDP_CODEC_CAPROGRESSIVE:
    case GRD_RDP_CODEC_AVC420:
      rdp_frame->view_type = GRD_RDP_FRAME_VIEW_TYPE_MAIN;
      break;
    case GRD_RDP_CODEC_AVC444v2:
      rdp_frame->view_type =
        frame_upgrade ? GRD_RDP_FRAME_VIEW_TYPE_AUX
                      : GRD_RDP_FRAME_VIEW_TYPE_DUAL;
      break;
    }
}

static uint32_t
get_n_required_image_views (GrdRdpFrame *rdp_frame)
{
  GrdRdpRenderContext *render_context = rdp_frame->render_context;
  GrdRdpCodec codec = grd_rdp_render_context_get_codec (render_context);

  switch (codec)
    {
    case GRD_RDP_CODEC_CAPROGRESSIVE:
      return 1;
    case GRD_RDP_CODEC_AVC420:
    case GRD_RDP_CODEC_AVC444v2:
      return 2;
    }

  g_assert_not_reached ();
}

static uint32_t
get_n_image_views_to_be_encoded (GrdRdpFrame *rdp_frame)
{
  switch (grd_rdp_frame_get_avc_view_type (rdp_frame))
    {
    case GRD_RDP_FRAME_VIEW_TYPE_DUAL:
      return 2;
    case GRD_RDP_FRAME_VIEW_TYPE_MAIN:
    case GRD_RDP_FRAME_VIEW_TYPE_AUX:
      return 1;
    }

  g_assert_not_reached ();
}

static void
acquire_image_views (GrdRdpFrame *rdp_frame)
{
  uint32_t n_image_views = get_n_required_image_views (rdp_frame);
  uint32_t n_image_views_to_be_encoded =
    get_n_image_views_to_be_encoded (rdp_frame);
  uint32_t i;

  g_assert (n_image_views_to_be_encoded <= n_image_views);

  for (i = 0; i < n_image_views; ++i)
    {
      GrdRdpRenderContext *render_context = rdp_frame->render_context;
      GrdImageView *image_view;

      image_view = grd_rdp_render_context_acquire_image_view (render_context);
      rdp_frame->acquired_image_views =
        g_list_append (rdp_frame->acquired_image_views, image_view);

      if (i < n_image_views_to_be_encoded)
        g_queue_push_tail (rdp_frame->unused_image_views, image_view);
    }
}

static void
prepare_new_frame (GrdRdpFrame *rdp_frame)
{
  rdp_frame->pending_view_finalization = TRUE;

  set_view_type (rdp_frame, FALSE);
  acquire_image_views (rdp_frame);
}

static void
prepare_frame_upgrade (GrdRdpFrame *rdp_frame)
{
  GrdRdpRenderContext *render_context = rdp_frame->render_context;
  cairo_region_t *damage_region = NULL;
  GrdImageView *image_view = NULL;

  set_view_type (rdp_frame, TRUE);

  grd_rdp_render_context_fetch_progressive_render_state (render_context,
                                                         &image_view,
                                                         &damage_region);
  g_assert (image_view);
  g_assert (damage_region);

  rdp_frame->acquired_image_views =
    g_list_append (rdp_frame->acquired_image_views, image_view);
  g_queue_push_tail (rdp_frame->unused_image_views, image_view);

  rdp_frame->damage_region = damage_region;
}

GrdRdpFrame *
grd_rdp_frame_new (GrdRdpRenderContext *render_context,
                   GrdRdpBuffer        *src_buffer_new,
                   GrdRdpBuffer        *src_buffer_old,
                   GrdRdpFrameCallback  frame_picked_up,
                   GrdRdpFrameCallback  view_finalized,
                   GrdRdpFrameCallback  frame_submitted,
                   GrdRdpFrameCallback  frame_finalized,
                   gpointer             callback_user_data,
                   GDestroyNotify       user_data_destroy)
{
  GrdRdpFrame *rdp_frame;

  rdp_frame = g_new0 (GrdRdpFrame, 1);
  rdp_frame->render_context = render_context;
  rdp_frame->src_buffer_new = src_buffer_new;
  rdp_frame->src_buffer_old = src_buffer_old;

  rdp_frame->frame_picked_up = frame_picked_up;
  rdp_frame->view_finalized = view_finalized;
  rdp_frame->frame_submitted = frame_submitted;
  rdp_frame->frame_finalized = frame_finalized;
  rdp_frame->callback_user_data = callback_user_data;
  rdp_frame->user_data_destroy = user_data_destroy;

  rdp_frame->encode_context = grd_encode_context_new ();
  rdp_frame->unused_image_views = g_queue_new ();

  if (src_buffer_new)
    prepare_new_frame (rdp_frame);
  else
    prepare_frame_upgrade (rdp_frame);

  return rdp_frame;
}

static void
release_image_views (GrdRdpFrame *rdp_frame)
{
  GList *l;

  for (l = rdp_frame->acquired_image_views; l; l = l->next)
    {
      GrdImageView *image_view = l->data;

      grd_rdp_render_context_release_image_view (rdp_frame->render_context,
                                                 image_view);
    }
  g_clear_pointer (&rdp_frame->acquired_image_views, g_list_free);
}

void
grd_rdp_frame_free (GrdRdpFrame *rdp_frame)
{
  g_assert (!rdp_frame->bitstreams);
  g_assert (rdp_frame->renderer);

  if (rdp_frame->pending_view_finalization)
    finalize_view (rdp_frame);

  g_clear_pointer (&rdp_frame->encode_context, grd_encode_context_free);
  g_clear_pointer (&rdp_frame->damage_region, cairo_region_destroy);

  g_clear_pointer (&rdp_frame->unused_image_views, g_queue_free);
  release_image_views (rdp_frame);

  rdp_frame->frame_finalized (rdp_frame, rdp_frame->callback_user_data);
  g_clear_pointer (&rdp_frame->callback_user_data,
                   rdp_frame->user_data_destroy);

  g_free (rdp_frame);
}
