#include "ecore_drm2_private.h"

static Eina_Bool
_plane_format_supported(Ecore_Drm2_Plane_State *pstate, uint32_t format)
{
   Eina_Bool ret = EINA_FALSE;
   unsigned int i = 0;

   for (; i < pstate->num_formats; i++)
     {
        if (pstate->formats[i] == format)
          {
             ret = EINA_TRUE;
             break;
          }
     }

   return ret;
}

static void
_plane_cursor_size_get(int fd, int *width, int *height)
{
   uint64_t caps;
   int ret;

   if (width)
     {
        *width = 64;
        ret = sym_drmGetCap(fd, DRM_CAP_CURSOR_WIDTH, &caps);
        if (ret == 0) *width = caps;
     }
   if (height)
     {
        *height = 64;
        ret = sym_drmGetCap(fd, DRM_CAP_CURSOR_HEIGHT, &caps);
        if (ret == 0) *height = caps;
     }
}

EAPI Ecore_Drm2_Plane *
ecore_drm2_plane_assign(Ecore_Drm2_Output *output, Ecore_Drm2_Fb *fb, int x, int y)
{
   Eina_List *l;
   Ecore_Drm2_Plane *plane;
   Ecore_Drm2_Plane_State *pstate, *pstate_chosen = NULL;

   if (!_ecore_drm2_use_atomic) return NULL;

   /* use algo based on format, size, etc to find a plane this FB can go in */
   EINA_LIST_FOREACH(output->plane_states, l, pstate)
     {
        if (pstate->in_use)
          continue;

        /* test if this plane supports the given format */
        if (!_plane_format_supported(pstate, fb->format))
          continue;

        if (pstate->type.value == DRM_PLANE_TYPE_CURSOR)
          {
             int cw, ch;

             _plane_cursor_size_get(output->fd, &cw, &ch);

             /* check that this fb can fit in cursor plane */
             if ((fb->w != cw) || (fb->h != ch))
               continue;

             /* if we reach here, this FB can go on the cursor plane */
             pstate_chosen = pstate;
          }
        else if (pstate->type.value == DRM_PLANE_TYPE_OVERLAY)
          {
             /* there are no size checks for an overlay plane */
             pstate_chosen = pstate;
          }
        else if (pstate->type.value == DRM_PLANE_TYPE_PRIMARY)
          {
             if ((fb->w > output->current_mode->width) ||
                 (fb->h > output->current_mode->height))
               continue;

             /* if we reach here, this FB can go on the primary plane */
             pstate_chosen = pstate;
          }
     }

   if (pstate_chosen)
     {
        pstate = pstate_chosen;
        goto out;
     }
   return NULL;

out:
   /* create plane */
   plane = calloc(1, sizeof(Ecore_Drm2_Plane));
   if (!plane) return NULL;

   pstate->in_use = EINA_TRUE;
   pstate->cid.value = output->crtc_id;
   pstate->fid.value = fb->id;
   plane->fb = fb;

   pstate->sx.value = 0;
   pstate->sy.value = 0;
   pstate->sw.value = fb->w << 16;
   pstate->sh.value = fb->h << 16;

   pstate->cx.value = x;
   pstate->cy.value = y;
   pstate->cw.value = fb->w;
   pstate->ch.value = fb->h;

   plane->state = pstate;
   plane->type = pstate->type.value;
   plane->output = output;

   output->planes = eina_list_append(output->planes, plane);

   if (!_fb_atomic_flip_test(output))
     {
        output->planes = eina_list_remove(output->planes, plane);
        plane->state->in_use = EINA_FALSE;
        free(plane);

        return NULL;
     }

   _ecore_drm2_fb_ref(fb);
   DBG("FB %d assigned to Plane %d", fb->id, pstate->obj_id);

   if (fb->status_handler)
     fb->status_handler(fb,
                        ECORE_DRM2_FB_STATUS_PLANE_ASSIGN,
                        fb->status_data);
   return plane;
}

EAPI void
ecore_drm2_plane_release(Ecore_Drm2_Plane *plane)
{
   Ecore_Drm2_Fb *fb;

   EINA_SAFETY_ON_NULL_RETURN(plane);
   EINA_SAFETY_ON_TRUE_RETURN(plane->dead);

   fb = plane->fb;

   plane->output->fbs =
     eina_list_append(plane->output->fbs, fb);

   plane->dead = EINA_TRUE;
   plane->state->in_use = EINA_FALSE;
   _fb_atomic_flip_test(plane->output);

   if (fb->status_handler)
     fb->status_handler(fb,
                        ECORE_DRM2_FB_STATUS_PLANE_RELEASE,
                        fb->status_data);
}

EAPI void
ecore_drm2_plane_destination_set(Ecore_Drm2_Plane *plane, int x, int y, int w, int h)
{
   EINA_SAFETY_ON_NULL_RETURN(plane);
   EINA_SAFETY_ON_TRUE_RETURN(plane->dead);

   plane->state->cx.value = x;
   plane->state->cy.value = y;
   plane->state->cw.value = w;
   plane->state->ch.value = h;

   _fb_atomic_flip_test(plane->output);
}

EAPI Eina_Bool
ecore_drm2_plane_fb_set(Ecore_Drm2_Plane *plane, Ecore_Drm2_Fb *fb)
{
   uint32_t fallback_id;

   EINA_SAFETY_ON_NULL_RETURN_VAL(plane, EINA_FALSE);
   EINA_SAFETY_ON_NULL_RETURN_VAL(fb, EINA_FALSE);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(plane->dead, EINA_FALSE);

   fallback_id = plane->state->fid.value;
   plane->state->fid.value = fb->id;

   /* Update plane state based on fb */
   plane->state->sw.value = fb->w << 16;
   plane->state->sh.value = fb->h << 16;
   plane->state->cw.value = fb->w;
   plane->state->ch.value = fb->h;

   if (_fb_atomic_flip_test(plane->output))
     {
        _ecore_drm2_fb_ref(fb);

        plane->output->fbs =
          eina_list_append(plane->output->fbs, plane->fb);

        plane->fb = fb;
        return EINA_TRUE;
     }
   plane->state->fid.value = fallback_id;
   return EINA_FALSE;
}
