#include "ecore_drm2_private.h"

#define INSIDE(x, y, xx, yy, ww, hh) \
   (((x) < ((xx) + (ww))) && ((y) < ((yy) + (hh))) && \
       ((x) >= (xx)) && ((y) >= (yy)))

#define EDID_DESCRIPTOR_ALPHANUMERIC_DATA_STRING 0xfe
#define EDID_DESCRIPTOR_DISPLAY_PRODUCT_NAME 0xfc
#define EDID_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER 0xff
#define EDID_OFFSET_DATA_BLOCKS 0x36
#define EDID_OFFSET_LAST_BLOCK 0x6c
#define EDID_OFFSET_PNPID 0x08
#define EDID_OFFSET_SERIAL 0x0c

static const char *conn_types[] =
{
   "None", "VGA", "DVI-I", "DVI-D", "DVI-A",
   "Composite", "S-Video", "LVDS", "Component", "DIN",
   "DisplayPort", "HDMI-A", "HDMI-B", "TV", "eDP", "Virtual", "DSI",
};

static void
_output_debug(Ecore_Drm2_Output *output, const drmModeConnector *conn)
{
   Eina_List *l;
   Ecore_Drm2_Output_Mode *omode;
   Ecore_Drm2_Plane_State *pstate;

   DBG("Created New Output At %d,%d", output->x, output->y);
   DBG("\tCrtc Pos: %d %d", output->ocrtc->x, output->ocrtc->y);
   DBG("\tCrtc: %d", output->crtc_id);
   DBG("\tConn: %d", output->conn_id);
   DBG("\tName: %s", output->name);
   DBG("\tMake: %s", output->make);
   DBG("\tModel: %s", output->model);
   DBG("\tSerial: %s", output->serial);
   DBG("\tCloned: %d", output->cloned);
   DBG("\tPrimary: %d", output->primary);
   DBG("\tConnected: %d", output->connected);
   DBG("\tEnabled: %d", output->enabled);

   if (output->backlight.path)
     {
        DBG("\tBacklight");
        switch (output->backlight.type)
          {
           case ECORE_DRM2_BACKLIGHT_RAW:
             DBG("\t\tType: Raw");
             break;
           case ECORE_DRM2_BACKLIGHT_PLATFORM:
             DBG("\t\tType: Platform");
             break;
           case ECORE_DRM2_BACKLIGHT_FIRMWARE:
             DBG("\t\tType: Firmware");
             break;
          }
        DBG("\t\tPath: %s", output->backlight.path);
     }

   EINA_LIST_FOREACH(output->plane_states, l, pstate)
     DBG("\tPossible Plane: %d", pstate->obj_id);

   EINA_LIST_FOREACH(output->modes, l, omode)
     {
        DBG("\tAdded Mode: %dx%d@%d%s%s%s",
            omode->width, omode->height, omode->refresh,
            (omode->flags & DRM_MODE_TYPE_PREFERRED) ? ", preferred" : "",
            (omode->flags & DRM_MODE_TYPE_DEFAULT) ? ", current" : "",
            (conn->count_modes == 0) ? ", built-in" : "");
     }
}

static void
_cb_output_event_free(void *data EINA_UNUSED, void *event)
{
   Ecore_Drm2_Event_Output_Changed *ev;

   ev = event;
   eina_stringshare_del(ev->make);
   eina_stringshare_del(ev->model);
   eina_stringshare_del(ev->name);
   free(ev);
}

static void
_output_event_send(Ecore_Drm2_Output *output)
{
   Ecore_Drm2_Event_Output_Changed *ev;

   ev = calloc(1, sizeof(Ecore_Drm2_Event_Output_Changed));
   if (!ev) return;

   ev->id = output->crtc_id;

   ev->x = output->x;
   ev->y = output->y;
   if (output->current_mode)
     {
        ev->w = output->current_mode->width;
        ev->h = output->current_mode->height;
        ev->refresh = output->current_mode->refresh;
     }
   else
     {
        ev->w = output->ocrtc->width;
        ev->h = output->ocrtc->height;
        ev->refresh = 0;
     }

   ev->phys_width = output->pw;
   ev->phys_height = output->ph;

   ev->scale = output->scale;
   ev->subpixel = output->subpixel;
   ev->transform = output->transform;
   ev->connected = output->connected;
   ev->enabled = output->enabled;

   ev->name = eina_stringshare_ref(output->name);
   ev->make = eina_stringshare_ref(output->make);
   ev->model = eina_stringshare_ref(output->model);

   ecore_event_add(ECORE_DRM2_EVENT_OUTPUT_CHANGED, ev,
                   _cb_output_event_free, NULL);
}

static void
_output_edid_parse_string(const uint8_t *data, char text[])
{
   int i = 0, rep = 0;

   strncpy(text, (const char *)data, 12);

   for (; text[i] != '\0'; i++)
     {
        if ((text[i] == '\n') || (text[i] == '\r'))
          {
             text[i] = '\0';
             break;
          }
     }

   for (i = 0; text[i] != '\0'; i++)
     {
        if (!isprint(text[i]))
          {
             text[i] = '-';
             rep++;
          }
     }

   if (rep > 4) text[0] = '\0';
}

static int
_output_edid_parse(Ecore_Drm2_Output *output, const uint8_t *data, size_t len)
{
   int i = 0;
   uint32_t serial;

   if (len < 128) return -1;
   if ((data[0] != 0x00) || (data[1] != 0xff)) return -1;

   output->edid.pnp[0] = 'A' + ((data[EDID_OFFSET_PNPID + 0] & 0x7c) / 4) - 1;
   output->edid.pnp[1] =
     'A' + ((data[EDID_OFFSET_PNPID + 0] & 0x3) * 8) +
     ((data[EDID_OFFSET_PNPID + 1] & 0xe0) / 32) - 1;
   output->edid.pnp[2] = 'A' + (data[EDID_OFFSET_PNPID + 1] & 0x1f) - 1;
   output->edid.pnp[3] = '\0';

   serial = (uint32_t) data[EDID_OFFSET_SERIAL + 0];
   serial += (uint32_t) data[EDID_OFFSET_SERIAL + 1] * 0x100;
   serial += (uint32_t) data[EDID_OFFSET_SERIAL + 2] * 0x10000;
   serial += (uint32_t) data[EDID_OFFSET_SERIAL + 3] * 0x1000000;
   if (serial > 0)
     sprintf(output->edid.serial, "%lu", (unsigned long)serial);

   for (i = EDID_OFFSET_DATA_BLOCKS; i <= EDID_OFFSET_LAST_BLOCK; i += 18)
     {
        if (data[i] != 0) continue;
        if (data[i + 2] != 0) continue;

        if (data[i + 3] == EDID_DESCRIPTOR_DISPLAY_PRODUCT_NAME)
          _output_edid_parse_string(&data[i + 5], output->edid.monitor);
        else if (data[i + 3] == EDID_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER)
          _output_edid_parse_string(&data[i + 5], output->edid.serial);
        else if (data[i + 3] == EDID_DESCRIPTOR_ALPHANUMERIC_DATA_STRING)
          _output_edid_parse_string(&data[i + 5], output->edid.eisa);
     }

   return 0;
}

static void
_output_edid_atomic_find(Ecore_Drm2_Output *output)
{
   Ecore_Drm2_Connector_State *cstate;
   int ret = 0;

   cstate = output->conn_state;

   ret = _output_edid_parse(output, cstate->edid.data, cstate->edid.len);
   if (!ret)
     {
        if (output->edid.pnp[0] != '\0')
          eina_stringshare_replace(&output->make, output->edid.pnp);
        if (output->edid.monitor[0] != '\0')
          eina_stringshare_replace(&output->model, output->edid.monitor);
        if (output->edid.serial[0] != '\0')
          eina_stringshare_replace(&output->serial, output->edid.serial);
     }
}

static void
_output_edid_find(Ecore_Drm2_Output *output, const drmModeConnector *conn)
{
   drmModePropertyBlobPtr blob = NULL;
   drmModePropertyPtr prop;
   int i = 0, ret = 0;

   for (; i < conn->count_props && !blob; i++)
     {
        if (!(prop = sym_drmModeGetProperty(output->fd, conn->props[i])))
          continue;
        if ((prop->flags & DRM_MODE_PROP_BLOB) &&
            (!strcmp(prop->name, "EDID")))
          {
             blob = sym_drmModeGetPropertyBlob(output->fd, conn->prop_values[i]);
          }
        sym_drmModeFreeProperty(prop);
        if (blob) break;
     }

   if (!blob) return;

   output->edid.blob = eina_memdup(blob->data, blob->length, 1);

   ret = _output_edid_parse(output, blob->data, blob->length);
   if (!ret)
     {
        if (output->edid.pnp[0] != '\0')
          eina_stringshare_replace(&output->make, output->edid.pnp);
        if (output->edid.monitor[0] != '\0')
          eina_stringshare_replace(&output->model, output->edid.monitor);
        if (output->edid.serial[0] != '\0')
          eina_stringshare_replace(&output->serial, output->edid.serial);
     }

   sym_drmModeFreePropertyBlob(blob);
}

static int
_output_crtc_find(const drmModeRes *res, const drmModeConnector *conn, int fd)
{
   drmModeEncoder *enc;
   uint32_t crtc;
   int i = 0, j = 0;

   /* Skip all disconnected connectors...
    *
    * When a connector is disconnected it still has an encoder id
    * which messes up our output selection code later.  When we support
    * multi-head properly and hotplug becomes a real thing we'll
    * need to revisit this hack (and the crtc assignment code as well)
    */
   if (conn->connection != DRM_MODE_CONNECTED) return -1;

   for (j = 0; j < conn->count_encoders; j++)
     {
        enc = sym_drmModeGetEncoder(fd, conn->encoders[j]);
        if (!enc) continue;

        crtc = enc->crtc_id;
        sym_drmModeFreeEncoder(enc);

        for (i = 0; i < res->count_crtcs; i++)
          if (crtc == res->crtcs[i])
            return i;
     }

   return -1;
}

static char *
_output_name_get(const drmModeConnector *conn)
{
   char name[DRM_CONNECTOR_NAME_LEN];
   const char *type = NULL;

   if (conn->connector_type < EINA_C_ARRAY_LENGTH(conn_types))
     type = conn_types[conn->connector_type];
   else
     type = "UNKNOWN";

   snprintf(name, sizeof(name), "%s-%d", type, conn->connector_type_id);
   return strdup(name);
}

static Ecore_Drm2_Output_Mode *
_output_mode_add(Ecore_Drm2_Output *output, const drmModeModeInfo *info)
{
   Ecore_Drm2_Output_Mode *mode;
   uint64_t refresh;

   EINA_SAFETY_ON_NULL_RETURN_VAL(info, NULL);
   EINA_SAFETY_ON_FALSE_RETURN_VAL((info->htotal > 0), NULL);
   EINA_SAFETY_ON_FALSE_RETURN_VAL((info->vtotal > 0), NULL);

   mode = calloc(1, sizeof(Ecore_Drm2_Output_Mode));
   if (!mode) return NULL;

   mode->flags = 0;
   mode->width = info->hdisplay;
   mode->height = info->vdisplay;

   refresh = (info->clock * 1000LL / info->htotal + info->vtotal / 2) /
     info->vtotal;

   if (info->flags & DRM_MODE_FLAG_INTERLACE)
     refresh *= 2;
   if (info->flags & DRM_MODE_FLAG_DBLSCAN)
     refresh /= 2;
   if (info->vscan > 1)
     refresh /= info->vscan;

   mode->refresh = refresh;
   mode->info = *info;

   if (info->type & DRM_MODE_TYPE_PREFERRED)
     mode->flags |= DRM_MODE_TYPE_PREFERRED;

   output->modes = eina_list_append(output->modes, mode);

   return mode;
}

static void
_output_modes_create(Ecore_Drm2_Device *dev, Ecore_Drm2_Output *output, const drmModeConnector *conn)
{
   int i = 0;
   drmModeCrtc *crtc;
   drmModeEncoder *enc;
   drmModeModeInfo crtc_mode;
   Ecore_Drm2_Output_Mode *omode;
   Ecore_Drm2_Output_Mode *current = NULL, *preferred = NULL, *best = NULL;
   Eina_List *l = NULL;

   memset(&crtc_mode, 0, sizeof(crtc_mode));

   enc = sym_drmModeGetEncoder(dev->fd, conn->encoder_id);
   if (enc)
     {
        crtc = sym_drmModeGetCrtc(dev->fd, enc->crtc_id);
        sym_drmModeFreeEncoder(enc);
        if (!crtc) return;
        if (crtc->mode_valid) crtc_mode = crtc->mode;
        sym_drmModeFreeCrtc(crtc);
     }

   for (i = 0; i < conn->count_modes; i++)
     {
        omode = _output_mode_add(output, &conn->modes[i]);
        if (!omode) continue;
     }

   EINA_LIST_REVERSE_FOREACH(output->modes, l, omode)
     {
        if (!memcmp(&crtc_mode, &omode->info, sizeof(crtc_mode)))
          current = omode;
        if (omode->flags & DRM_MODE_TYPE_PREFERRED)
          preferred = omode;
        best = omode;
     }

   if ((!current) && (crtc_mode.clock != 0))
     {
        current = _output_mode_add(output, &crtc_mode);
        if (!current) goto err;
     }

   if (current) output->current_mode = current;
   else if (preferred) output->current_mode = preferred;
   else if (best) output->current_mode = best;

   if (!output->current_mode) goto err;

   output->current_mode->flags |= DRM_MODE_TYPE_DEFAULT;

   return;

err:
   EINA_LIST_FREE(output->modes, omode)
     free(omode);
}

static drmModePropertyPtr
_output_dpms_property_get(int fd, const drmModeConnector *conn)
{
   drmModePropertyPtr prop;
   int i = 0;

   for (; i < conn->count_props; i++)
     {
        prop = sym_drmModeGetProperty(fd, conn->props[i]);
        if (!prop) continue;

        if (!strcmp(prop->name, "DPMS")) return prop;

        sym_drmModeFreeProperty(prop);
     }

   return NULL;
}

static double
_output_backlight_value_get(Ecore_Drm2_Output *output, const char *attr)
{
   const char *b = NULL;
   double ret = 0.0;

   if ((!output) || (!output->backlight.path)) return 0.0;

   b = eeze_udev_syspath_get_sysattr(output->backlight.path, attr);
   if (!b) return 0.0;

   ret = strtod(b, NULL);
   if (ret < 0) ret = 0.0;

   return ret;
}

static void
_output_backlight_init(Ecore_Drm2_Output *output, unsigned int conn_type)
{
   Eina_List *devs, *l;
   const char *dev, *t;
   Eina_Bool found = EINA_FALSE;
   Ecore_Drm2_Backlight_Type type = 0;

   devs = eeze_udev_find_by_filter("backlight", NULL, NULL);

   EINA_LIST_FOREACH(devs, l, dev)
     {
        t = eeze_udev_syspath_get_sysattr(dev, "type");
        if (!t) continue;

        if (!strcmp(t, "raw"))
          type = ECORE_DRM2_BACKLIGHT_RAW;
        else if (!strcmp(t, "platform"))
          type = ECORE_DRM2_BACKLIGHT_PLATFORM;
        else if (!strcmp(t, "firmware"))
          type = ECORE_DRM2_BACKLIGHT_FIRMWARE;

        if ((conn_type == DRM_MODE_CONNECTOR_LVDS) ||
            (conn_type == DRM_MODE_CONNECTOR_eDP) ||
            (type == ECORE_DRM2_BACKLIGHT_RAW))
          found = EINA_TRUE;

        eina_stringshare_del(t);
        if (found) break;
     }

   if (found)
     {
        output->backlight.type = type;
        output->backlight.path = eina_stringshare_add(dev);
        output->backlight.max =
          _output_backlight_value_get(output, "max_brightness");
        output->backlight.value =
          _output_backlight_value_get(output, "brightness");
     }

   EINA_LIST_FREE(devs, dev)
     eina_stringshare_del(dev);
}

static void
_output_scale_init(Ecore_Drm2_Output *output, Ecore_Drm2_Transform transform, unsigned int scale)
{
   output->transform = transform;

   if ((output->enabled) && (output->current_mode))
     {
        switch (transform)
          {
           case ECORE_DRM2_TRANSFORM_90:
           case ECORE_DRM2_TRANSFORM_270:
           case ECORE_DRM2_TRANSFORM_FLIPPED_90:
           case ECORE_DRM2_TRANSFORM_FLIPPED_270:
             output->w = output->current_mode->height;
             output->h = output->current_mode->width;
             break;
           case ECORE_DRM2_TRANSFORM_NORMAL:
           case ECORE_DRM2_TRANSFORM_180:
           case ECORE_DRM2_TRANSFORM_FLIPPED:
           case ECORE_DRM2_TRANSFORM_FLIPPED_180:
             output->w = output->current_mode->width;
             output->h = output->current_mode->height;
             break;
           default:
             break;
          }
     }

   output->scale = scale;
   output->w /= scale;
   output->h /= scale;
}

static void
_output_matrix_rotate_xy(Eina_Matrix3 *matrix, double x, double y)
{
   Eina_Matrix4 tmp, m;

   eina_matrix4_identity(&tmp);
   eina_matrix4_values_set(&tmp, x, y, 0, 0, -y, x, 0, 0,
                           0, 0, 1, 0, 0, 0, 0, 1);

   eina_matrix3_matrix4_to(&m, matrix);
   eina_matrix4_multiply(&m, &m, &tmp);
   eina_matrix4_matrix3_to(matrix, &m);
}

static void
_output_matrix_update(Ecore_Drm2_Output *output)
{
   Eina_Matrix3 m3;

   eina_matrix4_identity(&output->matrix);
   eina_matrix4_matrix3_to(&m3, &output->matrix);
   eina_matrix3_translate(&m3, -output->x, -output->y);

   switch (output->transform)
     {
      case ECORE_DRM2_TRANSFORM_FLIPPED:
      case ECORE_DRM2_TRANSFORM_FLIPPED_90:
      case ECORE_DRM2_TRANSFORM_FLIPPED_180:
      case ECORE_DRM2_TRANSFORM_FLIPPED_270:
        eina_matrix3_translate(&m3, -output->w, 0);
        break;
      default:
        break;
     }

   switch (output->transform)
     {
      case ECORE_DRM2_TRANSFORM_NORMAL:
      case ECORE_DRM2_TRANSFORM_FLIPPED:
      default:
        break;
      case ECORE_DRM2_TRANSFORM_90:
      case ECORE_DRM2_TRANSFORM_FLIPPED_90:
        eina_matrix3_translate(&m3, 0, -output->h);
        _output_matrix_rotate_xy(&m3, 0, 1);
        break;
      case ECORE_DRM2_TRANSFORM_180:
      case ECORE_DRM2_TRANSFORM_FLIPPED_180:
        eina_matrix3_translate(&m3, -output->w, -output->h);
        _output_matrix_rotate_xy(&m3, -1, 0);
        break;
      case ECORE_DRM2_TRANSFORM_270:
      case ECORE_DRM2_TRANSFORM_FLIPPED_270:
        eina_matrix3_translate(&m3, -output->w, 0);
        _output_matrix_rotate_xy(&m3, 0, -1);
        break;
     }

   if (output->scale != 1)
     eina_matrix3_scale(&m3, output->scale, output->scale);

   eina_matrix3_matrix4_to(&output->matrix, &m3);
   eina_matrix4_inverse(&output->inverse, &output->matrix);
}

static Ecore_Drm2_Crtc_State *
_atomic_state_crtc_duplicate(Ecore_Drm2_Crtc_State *state)
{
   Ecore_Drm2_Crtc_State *cstate;

   cstate = calloc(1, sizeof(Ecore_Drm2_Crtc_State));
   if (!cstate) return NULL;

   memcpy(cstate, state, sizeof(Ecore_Drm2_Crtc_State));

   return cstate;
}

static Ecore_Drm2_Crtc_State *
_output_crtc_state_get(Ecore_Drm2_Atomic_State *state, unsigned int id)
{
   Ecore_Drm2_Crtc_State *cstate;
   int i = 0;

   for (; i < state->crtcs; i++)
     {
        cstate = &state->crtc_states[i];
        if (cstate->obj_id != id) continue;
        return _atomic_state_crtc_duplicate(cstate);
     }

   return NULL;
}

static Ecore_Drm2_Connector_State *
_atomic_state_conn_duplicate(Ecore_Drm2_Connector_State *state)
{
   Ecore_Drm2_Connector_State *cstate;

   cstate = calloc(1, sizeof(Ecore_Drm2_Connector_State));
   if (!cstate) return NULL;

   memcpy(cstate, state, sizeof(Ecore_Drm2_Connector_State));

   return cstate;
}

static Ecore_Drm2_Connector_State *
_output_conn_state_get(Ecore_Drm2_Atomic_State *state, unsigned int id)
{
   Ecore_Drm2_Connector_State *cstate;
   int i = 0;

   for (; i < state->conns; i++)
     {
        cstate = &state->conn_states[i];
        if (cstate->obj_id != id) continue;
        return _atomic_state_conn_duplicate(cstate);
     }

   return NULL;
}

static Ecore_Drm2_Plane_State *
_atomic_state_plane_duplicate(Ecore_Drm2_Plane_State *state)
{
   Ecore_Drm2_Plane_State *pstate;

   pstate = calloc(1, sizeof(Ecore_Drm2_Plane_State));
   if (!pstate) return NULL;

   memcpy(pstate, state, sizeof(Ecore_Drm2_Plane_State));

   return pstate;
}

static Eina_List *
_output_plane_states_get(Ecore_Drm2_Atomic_State *state, unsigned int crtc_id, int index)
{
   Eina_List *states = NULL;
   Ecore_Drm2_Plane_State *pstate;

   int i = 0;

   for (; i < state->planes; i++)
     {
        pstate = &state->plane_states[i];
        if (pstate->cid.value == crtc_id)
          {
             states =
               eina_list_append(states, _atomic_state_plane_duplicate(pstate));
          }
        else if (pstate->mask & (1ULL << index))
          {
             states =
               eina_list_append(states, _atomic_state_plane_duplicate(pstate));
          }
     }

   return states;
}

static Eina_Bool
_output_create(Ecore_Drm2_Device *dev, const drmModeRes *res, const drmModeConnector *conn, int x, int y, int *w, Eina_Bool cloned)
{
   Ecore_Drm2_Output *output;
   int i = 0;
   char *name = NULL;

   if (w) *w = 0;

   i = _output_crtc_find(res, conn, dev->fd);
   if (i < 0) return EINA_FALSE;

   output = calloc(1, sizeof(Ecore_Drm2_Output));
   if (!output) return EINA_FALSE;

   output->fd = dev->fd;
   output->x = x;
   output->y = y;
   output->cloned = cloned;
   output->pw = conn->mmWidth;
   output->ph = conn->mmHeight;

   switch (conn->subpixel)
     {
      case DRM_MODE_SUBPIXEL_UNKNOWN:
        output->subpixel = 0; // WL_OUTPUT_SUBPIXEL_UNKNOWN
        break;
      case DRM_MODE_SUBPIXEL_NONE:
        output->subpixel = 1; // WL_OUTPUT_SUBPIXEL_NONE
        break;
      case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB:
        output->subpixel = 2; // WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB
        break;
      case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR:
        output->subpixel = 3; // WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR
        break;
      case DRM_MODE_SUBPIXEL_VERTICAL_RGB:
        output->subpixel = 4; // WL_OUTPUT_SUBPIXEL_VERTICAL_RGB
        break;
      case DRM_MODE_SUBPIXEL_VERTICAL_BGR:
        output->subpixel = 5; // WL_OUTPUT_SUBPIXEL_VERTICAL_BGR
        break;
      default:
        output->subpixel = 0;
        break;
     }

   name = _output_name_get(conn);
   output->name = eina_stringshare_add(name);
   output->make = eina_stringshare_add("unknown");
   output->model = eina_stringshare_add("unknown");
   output->serial = eina_stringshare_add("unknown");
   free(name);

   output->pipe = i;
   output->crtc_id = res->crtcs[i];
   output->conn_id = conn->connector_id;
   output->conn_type = conn->connector_type;

   output->connected = (conn->connection == DRM_MODE_CONNECTED);

   output->ocrtc = sym_drmModeGetCrtc(dev->fd, output->crtc_id);

   if (_ecore_drm2_use_atomic)
     {
        output->crtc_state =
          _output_crtc_state_get(dev->state, output->crtc_id);
        output->conn_state =
          _output_conn_state_get(dev->state, output->conn_id);
        output->plane_states =
          _output_plane_states_get(dev->state, output->crtc_id, output->pipe);
     }

   output->dpms = _output_dpms_property_get(dev->fd, conn);

   _output_backlight_init(output, conn->connector_type);

   output->gamma = output->ocrtc->gamma_size;

   _output_modes_create(dev, output, conn);

   if (_ecore_drm2_use_atomic)
     _output_edid_atomic_find(output);
   else
     _output_edid_find(output, conn);

   if (output->connected) output->enabled = EINA_TRUE;

   _output_scale_init(output, ECORE_DRM2_TRANSFORM_NORMAL, 1);
   _output_matrix_update(output);

   if (!eina_list_count(dev->outputs))
     output->primary = EINA_TRUE;
   else
     {
        /* temporarily disable other outputs which are not primary */
        output->connected = EINA_FALSE;
        output->enabled = EINA_FALSE;
     }

   dev->outputs = eina_list_append(dev->outputs, output);

   _output_debug(output, conn);

   if ((output->enabled) && (output->current_mode))
     {
        if (w) *w = output->current_mode->width;
     }

   return EINA_TRUE;
}

static Ecore_Drm2_Output *
_output_find_by_con(Ecore_Drm2_Device *dev, uint32_t id)
{
   Ecore_Drm2_Output *output;
   Eina_List *l;

   EINA_LIST_FOREACH(dev->outputs, l, output)
     if (output->conn_id == id) return output;

   return NULL;
}

static void
_outputs_update(Ecore_Drm2_Device *dev)
{
   Ecore_Drm2_Output *output;
   Eina_List *l, *ll;
   drmModeRes *res;
   drmModeConnector *conn;
   uint32_t *connected;
   int i = 0, x = 0, y = 0;

   res = sym_drmModeGetResources(dev->fd);
   if (!res) return;

   connected = calloc(res->count_connectors, sizeof(uint32_t));
   if (!connected)
     {
        sym_drmModeFreeResources(res);
        return;
     }

   for (i = 0; i < res->count_connectors; i++)
     {
        conn = sym_drmModeGetConnector(dev->fd, res->connectors[i]);
        if (!conn) continue;

        if (conn->connection != DRM_MODE_CONNECTED) goto next;

        connected[i] = res->connectors[i];
        if (!_output_find_by_con(dev, res->connectors[i]))
          {
             if (dev->outputs)
               {
                  Ecore_Drm2_Output *last;

                  last = eina_list_last_data_get(dev->outputs);
                  if (last) x = last->x + last->current_mode->width;
                  else x = 0;
               }
             else
               x = 0;

             if (!_output_create(dev, res, conn, x, y, NULL, EINA_TRUE))
               goto next;
          }

next:
        sym_drmModeFreeConnector(conn);
     }

   sym_drmModeFreeResources(res);

   EINA_LIST_FOREACH_SAFE(dev->outputs, l, ll, output)
     {
        Eina_Bool disconnected = EINA_TRUE;

        for (i = 0; i < res->count_connectors; i++)
          if (connected[i] == output->conn_id)
            {
               disconnected = EINA_FALSE;
               break;
            }

        if (disconnected)
          {
             output->connected = EINA_FALSE;
             output->enabled = EINA_FALSE;
          }
        else
          {
             output->connected = EINA_TRUE;
             output->enabled = EINA_TRUE;
          }

        _output_event_send(output);
     }
   free(connected);
}

static void
_cb_output_event(const char *device EINA_UNUSED, Eeze_Udev_Event event EINA_UNUSED, void *data, Eeze_Udev_Watch *watch EINA_UNUSED)
{
   Ecore_Drm2_Device *dev;

   dev = data;
   _outputs_update(dev);
}

static void
_output_destroy(Ecore_Drm2_Device *dev EINA_UNUSED, Ecore_Drm2_Output *output)
{
   Ecore_Drm2_Output_Mode *mode;
   Ecore_Drm2_Plane *plane;
   Ecore_Drm2_Plane_State *pstate;

   if (_ecore_drm2_use_atomic)
     {
        if (output->prep.atomic_req)
          sym_drmModeAtomicFree(output->prep.atomic_req);

        EINA_LIST_FREE(output->plane_states, pstate)
          free(pstate);

        EINA_LIST_FREE(output->planes, plane)
          free(plane);

        free(output->conn_state);
        free(output->crtc_state);
     }

   EINA_LIST_FREE(output->modes, mode)
     {
        if (mode->id)
          sym_drmModeDestroyPropertyBlob(output->fd, mode->id);
        free(mode);
     }

   eina_stringshare_del(output->backlight.path);
   eina_stringshare_del(output->name);
   eina_stringshare_del(output->make);
   eina_stringshare_del(output->model);
   eina_stringshare_del(output->serial);
   eina_stringshare_del(output->relative.to);

   if (output->flip_timeout) ecore_timer_del(output->flip_timeout);

   sym_drmModeFreeProperty(output->dpms);
   free(output->edid.blob);

   free(output);
}

/* this function is used to indicate if we are in a multi-gpu situation
 * and need to calculate vblank sync with high crtc mask */
static unsigned int
_output_vblank_pipe(Ecore_Drm2_Output *output)
{
   if (output->pipe > 1)
     return ((output->pipe << DRM_VBLANK_HIGH_CRTC_SHIFT) &
             DRM_VBLANK_HIGH_CRTC_MASK);
   else if (output->pipe > 0)
     return DRM_VBLANK_SECONDARY;
   else
     return 0;
}

EAPI Eina_Bool
ecore_drm2_outputs_create(Ecore_Drm2_Device *device)
{
   drmModeConnector *conn;
   drmModeRes *res;
   int i = 0, x = 0, y = 0, w = 0;
   int events = 0;

   EINA_SAFETY_ON_NULL_RETURN_VAL(device, EINA_FALSE);
   EINA_SAFETY_ON_TRUE_RETURN_VAL((device->fd < 0), EINA_FALSE);

   res = sym_drmModeGetResources(device->fd);
   if (!res) return EINA_FALSE;

   device->crtcs = calloc(res->count_crtcs, sizeof(uint32_t));
   if (!device->crtcs) goto err;

   device->min.width = res->min_width;
   device->min.height = res->min_height;
   device->max.width = res->max_width;
   device->max.height = res->max_height;

   device->num_crtcs = res->count_crtcs;
   memcpy(device->crtcs, res->crtcs, sizeof(uint32_t) * res->count_crtcs);

   for (i = 0; i < res->count_connectors; i++)
     {
        conn = sym_drmModeGetConnector(device->fd, res->connectors[i]);
        if (!conn) continue;

        if (!_output_create(device, res, conn, x, y, &w, EINA_FALSE))
          goto next;

        x += w;

next:
        sym_drmModeFreeConnector(conn);
     }

   if (eina_list_count(device->outputs) < 1) goto err;

   sym_drmModeFreeResources(res);

   events = (EEZE_UDEV_EVENT_ADD | EEZE_UDEV_EVENT_REMOVE |
             EEZE_UDEV_EVENT_CHANGE);

   device->watch =
     eeze_udev_watch_add(EEZE_UDEV_TYPE_DRM, events, _cb_output_event, device);

   return EINA_TRUE;

err:
   sym_drmModeFreeResources(res);
   return EINA_FALSE;
}

EAPI void
ecore_drm2_outputs_destroy(Ecore_Drm2_Device *device)
{
   Ecore_Drm2_Output *output;

   EINA_SAFETY_ON_NULL_RETURN(device);

   EINA_LIST_FREE(device->outputs, output)
     _output_destroy(device, output);
}

EAPI const Eina_List *
ecore_drm2_outputs_get(Ecore_Drm2_Device *device)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(device, NULL);
   return device->outputs;
}

EAPI int
ecore_drm2_output_dpms_get(Ecore_Drm2_Output *output)
{
   drmModeObjectProperties *props;
   drmModePropertyRes *prop;
   int val = -1;
   unsigned int i;

   EINA_SAFETY_ON_NULL_RETURN_VAL(output, -1);

   props =
     sym_drmModeObjectGetProperties(output->fd, output->conn_id,
                                    DRM_MODE_OBJECT_CONNECTOR);
   if (!props) return -1;

   for (i = 0; i < props->count_props; i++)
     {
        prop = sym_drmModeGetProperty(output->fd, props->props[i]);
        if (!prop) continue;

        if (!strcmp(prop->name, "DPMS"))
          val = props->prop_values[i];

        sym_drmModeFreeProperty(prop);
     }

   sym_drmModeFreeObjectProperties(props);

   return val;
}

EAPI void
ecore_drm2_output_dpms_set(Ecore_Drm2_Output *output, int level)
{
   EINA_SAFETY_ON_NULL_RETURN(output);
   EINA_SAFETY_ON_TRUE_RETURN(!output->enabled);

   sym_drmModeConnectorSetProperty(output->fd, output->conn_id,
                                   output->dpms->prop_id, level);

   if (level == 0) /* DPMS on */
     ecore_drm2_fb_flip(NULL, output);
}

EAPI char *
ecore_drm2_output_edid_get(Ecore_Drm2_Output *output)
{
   char *edid_str = NULL;
   unsigned char *blob;
   unsigned char fallback_blob[128];

   EINA_SAFETY_ON_NULL_RETURN_VAL(output, NULL);

   if (_ecore_drm2_use_atomic)
     blob = output->conn_state->edid.data;
   else
     {
        EINA_SAFETY_ON_NULL_RETURN_VAL(output->edid.blob, NULL);
        blob = output->edid.blob;
     }
   if (!blob)
     {
        memset(fallback_blob, 0, sizeof(fallback_blob));
        blob = fallback_blob;
     }

   edid_str = malloc((128 * 2) + 1);
   if (edid_str)
     {
        unsigned int k, kk;
        const char *hexch = "0123456789abcdef";

        for (kk = 0, k = 0; k < 128; k++)
          {
             edid_str[kk] = hexch[(blob[k] >> 4) & 0xf];
             edid_str[kk + 1] = hexch[blob[k] & 0xf];
             kk += 2;
          }
        edid_str[kk] = 0;
     }

   return edid_str;
}

EAPI Eina_Bool
ecore_drm2_output_backlight_get(Ecore_Drm2_Output *output)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(output, EINA_FALSE);
   return (output->backlight.path != NULL);
}

EAPI Ecore_Drm2_Output *
ecore_drm2_output_find(Ecore_Drm2_Device *device, int x, int y)
{
   Eina_List *l;
   Ecore_Drm2_Output *output;

   EINA_SAFETY_ON_NULL_RETURN_VAL(device, NULL);

   EINA_LIST_FOREACH(device->outputs, l, output)
     {
        int ox, oy, ow, oh;

        if (!output->enabled) continue;

        ox = output->x;
        oy = output->y;
        ow = output->current_mode->width;
        oh = output->current_mode->height;

        if (INSIDE(x, y, ox, oy, ow, oh))
          return output;
     }

   return NULL;
}

EAPI void
ecore_drm2_output_dpi_get(Ecore_Drm2_Output *output, int *xdpi, int *ydpi)
{
   EINA_SAFETY_ON_NULL_RETURN(output);
   EINA_SAFETY_ON_TRUE_RETURN(!output->enabled);

   if (xdpi)
     *xdpi = ((25.4 * (output->current_mode->width)) / output->pw);

   if (ydpi)
     *ydpi = ((25.4 * (output->current_mode->height)) / output->ph);
}

EAPI unsigned int
ecore_drm2_output_crtc_get(Ecore_Drm2_Output *output)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(output, 0);
   return output->crtc_id;
}

EAPI Ecore_Drm2_Fb *
ecore_drm2_output_latest_fb_get(Ecore_Drm2_Output *output)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(output, NULL);
   if (output->pending.fb) return output->pending.fb;
   if (output->current.fb) return output->current.fb;
   return output->next.fb;
}

EAPI Eina_Bool
ecore_drm2_output_primary_get(Ecore_Drm2_Output *output)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(output, EINA_FALSE);
   return output->primary;
}

EAPI void
ecore_drm2_output_primary_set(Ecore_Drm2_Output *output, Eina_Bool primary)
{
   EINA_SAFETY_ON_NULL_RETURN(output);
   output->primary = primary;
}

EAPI Eina_Bool
ecore_drm2_output_enabled_get(Ecore_Drm2_Output *output)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(output, EINA_FALSE);
   return output->enabled;
}

EAPI void
ecore_drm2_output_enabled_set(Ecore_Drm2_Output *output, Eina_Bool enabled)
{
   EINA_SAFETY_ON_NULL_RETURN(output);

   if (!output->connected) return;
   if (output->enabled == enabled) return;

   if (enabled)
     {
        output->enabled = enabled;
        ecore_drm2_output_dpms_set(output, DRM_MODE_DPMS_ON);
     }
   else
     {
        if (_ecore_drm2_use_atomic)
          ecore_drm2_fb_flip(NULL, output);

        ecore_drm2_output_dpms_set(output, DRM_MODE_DPMS_OFF);
        output->enabled = enabled;

        if (output->current.fb)
          _ecore_drm2_fb_buffer_release(output, &output->current);

        if (output->next.fb)
          _ecore_drm2_fb_buffer_release(output, &output->next);

        if (output->pending.fb)
          _ecore_drm2_fb_buffer_release(output, &output->pending);
     }

   _output_event_send(output);
}

EAPI void
ecore_drm2_output_physical_size_get(Ecore_Drm2_Output *output, int *w, int *h)
{
   if (w) *w = 0;
   if (h) *h = 0;

   EINA_SAFETY_ON_NULL_RETURN(output);

   if (w) *w = output->pw;
   if (h) *h = output->ph;
}

EAPI const Eina_List *
ecore_drm2_output_modes_get(Ecore_Drm2_Output *output)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(output, NULL);
   return output->modes;
}

EAPI void
ecore_drm2_output_mode_info_get(Ecore_Drm2_Output_Mode *mode, int *w, int *h, unsigned int *refresh, unsigned int *flags)
{
   if (w) *w = 0;
   if (h) *h = 0;
   if (refresh) *refresh = 0;
   if (flags) *flags = 0;

   EINA_SAFETY_ON_NULL_RETURN(mode);

   if (w) *w = mode->width;
   if (h) *h = mode->height;
   if (refresh) *refresh = mode->refresh;
   if (flags) *flags = mode->flags;
}

static Eina_Bool
_output_mode_atomic_set(Ecore_Drm2_Output *output, Ecore_Drm2_Output_Mode *mode)
{
   Ecore_Drm2_Crtc_State *cstate;
   drmModeAtomicReq *req = NULL;
   int ret = 0;

   cstate = output->crtc_state;

   if (mode)
     {
        if (mode->id)
          sym_drmModeDestroyPropertyBlob(output->fd, mode->id);

        ret =
          sym_drmModeCreatePropertyBlob(output->fd, &mode->info,
                                        sizeof(drmModeModeInfo), &mode->id);
        if (ret < 0)
          {
             ERR("Failed to create Mode Property Blob");
             return EINA_FALSE;
          }
     }

   req = sym_drmModeAtomicAlloc();
   if (!req) return EINA_FALSE;

   sym_drmModeAtomicSetCursor(req, 0);

   if (mode)
     {
        cstate->active.value = 1;
        cstate->mode.value = mode->id;
     }
   else
     cstate->active.value = 0;

   ret = sym_drmModeAtomicAddProperty(req, cstate->obj_id, cstate->mode.id,
                                      cstate->mode.value);
   if (ret < 0)
     {
        ERR("Could not add atomic property");
        ret = EINA_FALSE;
        goto err;
     }

   ret = sym_drmModeAtomicAddProperty(req, cstate->obj_id,
                                      cstate->active.id, cstate->active.value);
   if (ret < 0)
     {
        ERR("Could not add atomic property");
        ret = EINA_FALSE;
        goto err;
     }

   if (cstate->background.id)
     {
        ret =
          sym_drmModeAtomicAddProperty(req, cstate->obj_id,
                                       cstate->background.id,
                                       cstate->background.value);
        if (ret < 0)
          {
             ERR("Could not add atomic property");
             ret = EINA_FALSE;
             goto err;
          }
     }

   ret = sym_drmModeAtomicCommit(output->fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET,
                                 output);
   if (ret < 0)
     {
        ERR("Failed to commit atomic Mode: %m");
        ret = EINA_FALSE;
        goto err;
     }
   else
     ret = EINA_TRUE;

err:
   sym_drmModeAtomicFree(req);
   return ret;
}

EAPI Eina_Bool
ecore_drm2_output_mode_set(Ecore_Drm2_Output *output, Ecore_Drm2_Output_Mode *mode, int x, int y)
{
   Eina_Bool ret = EINA_TRUE;

   EINA_SAFETY_ON_NULL_RETURN_VAL(output, EINA_FALSE);
   EINA_SAFETY_ON_TRUE_RETURN_VAL((output->fd < 0), EINA_FALSE);

   output->x = x;
   output->y = y;
   output->current_mode = mode;

   if (_ecore_drm2_use_atomic)
     ret = _output_mode_atomic_set(output, mode);
   else
     {
        if (mode)
          {
             unsigned int buffer = 0;

             if (output->current.fb)
               buffer = output->current.fb->id;
             else if (output->next.fb)
               buffer = output->next.fb->id;
             else
               buffer = output->ocrtc->buffer_id;

             if (sym_drmModeSetCrtc(output->fd, output->crtc_id, buffer, 0, 0,
                                    &output->conn_id, 1, &mode->info) < 0)
               {
                  ERR("Failed to set Mode %dx%d for Output %s: %m",
                      mode->width, mode->height, output->name);
                  ret = EINA_FALSE;
               }
          }
        else
          {
             if (sym_drmModeSetCrtc(output->fd, output->crtc_id, 0,
                                    0, 0, 0, 0, NULL) < 0)
               {
                  ERR("Failed to turn off Output %s: %m", output->name);
                  ret = EINA_FALSE;
               }
          }
     }

   return ret;
}

EAPI char *
ecore_drm2_output_name_get(Ecore_Drm2_Output *output)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(output, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(output->name, NULL);
   return strdup(output->name);
}

EAPI char *
ecore_drm2_output_model_get(Ecore_Drm2_Output *output)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(output, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(output->model, NULL);
   return strdup(output->model);
}

EAPI Eina_Bool
ecore_drm2_output_connected_get(Ecore_Drm2_Output *output)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(output, EINA_FALSE);
   return output->connected;
}

EAPI Eina_Bool
ecore_drm2_output_cloned_get(Ecore_Drm2_Output *output)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(output, EINA_FALSE);
   return (output->cloned ||
           output->relative.mode == ECORE_DRM2_RELATIVE_MODE_CLONE);
}

EAPI unsigned int
ecore_drm2_output_connector_type_get(Ecore_Drm2_Output *output)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(output, 0);
   return output->conn_type;
}

EAPI Eina_Bool
ecore_drm2_output_possible_crtc_get(Ecore_Drm2_Output *output, unsigned int crtc)
{
   drmModeRes *res;
   drmModeConnector *conn;
   drmModeEncoder *enc;
   int i = 0, j = 0, k = 0;
   unsigned int p = 0;
   Eina_Bool ret = EINA_FALSE;

   EINA_SAFETY_ON_NULL_RETURN_VAL(output, EINA_FALSE);
   EINA_SAFETY_ON_TRUE_RETURN_VAL((output->fd < 0), EINA_FALSE);

   res = sym_drmModeGetResources(output->fd);
   if (!res) return EINA_FALSE;

   for (; i < res->count_connectors; i++)
     {
        conn = sym_drmModeGetConnector(output->fd, res->connectors[i]);
        if (!conn) continue;

        for (j = 0; j < conn->count_encoders; j++)
          {
             enc = sym_drmModeGetEncoder(output->fd, conn->encoders[j]);
             if (!enc) continue;

             if (enc->crtc_id != crtc) goto next;

             p = enc->possible_crtcs;

             for (k = 0; k < res->count_crtcs; k++)
               {
                  if (res->crtcs[k] != output->crtc_id) continue;

                  if (p & (1ULL << k))
                    {
                       ret = EINA_TRUE;
                       break;
                    }
               }

next:
             sym_drmModeFreeEncoder(enc);
             if (ret) break;
          }

        sym_drmModeFreeConnector(conn);
        if (ret) break;
     }

   sym_drmModeFreeResources(res);

   return ret;
}

EAPI void
ecore_drm2_output_user_data_set(Ecore_Drm2_Output *o, void *data)
{
   EINA_SAFETY_ON_NULL_RETURN(o);

   o->user_data = data;
}

EAPI void *
ecore_drm2_output_user_data_get(Ecore_Drm2_Output *output)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(output, NULL);
   return output->user_data;
}

EAPI void
ecore_drm2_output_gamma_set(Ecore_Drm2_Output *output, uint16_t size, uint16_t *red, uint16_t *green, uint16_t *blue)
{
   EINA_SAFETY_ON_NULL_RETURN(output);
   EINA_SAFETY_ON_TRUE_RETURN(output->fd < 0);

   if (output->gamma != size) return;

   if (sym_drmModeCrtcSetGamma(output->fd, output->crtc_id, size,
                               red, green, blue) < 0)
     ERR("Failed to set gamma for Output %s: %m", output->name);
}

EAPI int
ecore_drm2_output_supported_rotations_get(Ecore_Drm2_Output *output)
{
   int ret = -1;

   EINA_SAFETY_ON_NULL_RETURN_VAL(output, -1);

   if (_ecore_drm2_use_atomic)
     {
        Ecore_Drm2_Plane_State *pstate;
        Eina_List *l;

        EINA_LIST_FOREACH(output->plane_states, l, pstate)
          {
             if (pstate->type.value != DRM_PLANE_TYPE_PRIMARY) continue;
             ret = pstate->supported_rotations;
             break;
          }
     }
   else
     return (ECORE_DRM2_ROTATION_NORMAL | ECORE_DRM2_ROTATION_90 |
             ECORE_DRM2_ROTATION_180 | ECORE_DRM2_ROTATION_270);

   return ret;
}

EAPI Eina_Bool
ecore_drm2_output_rotation_set(Ecore_Drm2_Output *output, int rotation)
{
   Eina_Bool ret = EINA_TRUE;

   EINA_SAFETY_ON_NULL_RETURN_VAL(output, EINA_FALSE);

   output->rotation = rotation;

#if 0
   /* XXX: Disable hardware plane rotation for now as this has broken
    * recently. The break happens because of an invalid argument,
    * ie: the value being sent from pstate->rotation_map ends up being
    * incorrect for some reason. I suspect the breakage to be from
    * kernel drivers (linux 4.20.0) but have not confirmed that version */
   if (_ecore_drm2_use_atomic)
     {
        Eina_List *l;
        Ecore_Drm2_Plane_State *pstate = NULL;
        drmModeAtomicReq *req = NULL;
        int res = 0;
        uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK |
          DRM_MODE_ATOMIC_ALLOW_MODESET;

        EINA_LIST_FOREACH(output->plane_states, l, pstate)
          {
             if (pstate->type.value != DRM_PLANE_TYPE_PRIMARY) continue;

             if ((pstate->supported_rotations & rotation) == 0)
               {
                  WRN("Unsupported rotation");
                  return EINA_FALSE;
               }

             req = sym_drmModeAtomicAlloc();
             if (!req) return EINA_FALSE;

             sym_drmModeAtomicSetCursor(req, 0);

             res = sym_drmModeAtomicAddProperty(req, pstate->obj_id,
                                                pstate->rotation.id, rotation);
             if (res < 0) goto err;

             res = sym_drmModeAtomicCommit(output->fd, req, flags, output);
             if (res < 0)
               goto err;
             else
               {
                  ret = EINA_TRUE;
                  pstate->rotation.value = rotation;
               }
          }

err:
        sym_drmModeAtomicFree(req);
     }
#endif

   return ret;
}

EAPI int
ecore_drm2_output_rotation_get(Ecore_Drm2_Output *output)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(output, -1);
   return output->rotation;
}

EAPI unsigned int
ecore_drm2_output_subpixel_get(const Ecore_Drm2_Output *output)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(output, 0);
   return output->subpixel;
}

static void
_blank_fallback_handler(int fd EINA_UNUSED, unsigned int frame EINA_UNUSED, unsigned int sec, unsigned int usec, void *data EINA_UNUSED)
{
   Ecore_Drm2_Output *output;

   output = data;
   output->fallback_usec = usec;
   output->fallback_sec = sec;
}

static int
_blanktime_fallback(Ecore_Drm2_Output *output, int sequence, long *sec, long *usec)
{
   drmEventContext ctx;
   int ret;

   /* Too lazy to loop for > 1, and don't want to block for < 1 */
   if (sequence != 1) return -1;

   /* If we got here with a flip waiting to complete we can do nothing. */
   if (output->pending.fb) return -1;

   if (!output->current.fb) return -1;

   memset(&ctx, 0, sizeof(ctx));
   ctx.version = 2;
   ctx.page_flip_handler = _blank_fallback_handler;
   ctx.vblank_handler = NULL;

   ret = sym_drmModePageFlip(output->current.fb->fd, output->crtc_id,
                             output->current.fb->id, DRM_MODE_PAGE_FLIP_EVENT,
                             output);
   if (ret < 0) return -1;
   do
     {
        ret = sym_drmHandleEvent(output->current.fb->fd, &ctx);
     } while (ret != 0 && errno == EAGAIN);
   if (ret < 0) return -1;

   *sec = output->fallback_sec;
   *usec = output->fallback_usec;
   return 0;
}

EAPI Eina_Bool
ecore_drm2_output_blanktime_get(Ecore_Drm2_Output *output, int sequence, long *sec, long *usec)
{
   drmVBlank v;
   int ret;
   Eina_Bool success;

   EINA_SAFETY_ON_NULL_RETURN_VAL(output, EINA_FALSE);
   EINA_SAFETY_ON_NULL_RETURN_VAL(sec, EINA_FALSE);
   EINA_SAFETY_ON_NULL_RETURN_VAL(usec, EINA_FALSE);

   memset(&v, 0, sizeof(v));
   v.request.type = DRM_VBLANK_RELATIVE;
   v.request.type |= _output_vblank_pipe(output);
   v.request.sequence = sequence;
   ret = sym_drmWaitVBlank(output->fd, &v);
   success = (ret == 0) && (v.reply.tval_sec > 0 || v.reply.tval_usec > 0);
   if (!success)
     {
        ret = _blanktime_fallback(output, sequence, sec, usec);
        if (ret) return EINA_FALSE;
        return EINA_TRUE;
     }

   *sec = v.reply.tval_sec;
   *usec = v.reply.tval_usec;
   return EINA_TRUE;
}

EAPI void
ecore_drm2_output_info_get(Ecore_Drm2_Output *output, int *x, int *y, int *w, int *h, unsigned int *refresh)
{
   if (x) *x = 0;
   if (y) *y = 0;
   if (w) *w = 0;
   if (h) *h = 0;
   if (refresh) *refresh = 0;

   EINA_SAFETY_ON_NULL_RETURN(output);
   EINA_SAFETY_ON_TRUE_RETURN(!output->current_mode);

   switch (output->rotation)
     {
      case ECORE_DRM2_ROTATION_90:
      case ECORE_DRM2_ROTATION_270:
        if (w) *w = output->current_mode->height;
        if (h) *h = output->current_mode->width;
        break;
      case ECORE_DRM2_ROTATION_NORMAL:
      case ECORE_DRM2_ROTATION_180:
      default:
        if (w) *w = output->current_mode->width;
        if (h) *h = output->current_mode->height;
        break;
     }

   if (refresh) *refresh = output->current_mode->refresh;
   if (x) *x = output->x;
   if (y) *y = output->y;
}

EAPI Eina_Bool
ecore_drm2_output_pending_get(Ecore_Drm2_Output *output)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(output, EINA_FALSE);

   if (output->pending.fb) return EINA_TRUE;

   return EINA_FALSE;
}

EAPI void
ecore_drm2_output_relative_mode_set(Ecore_Drm2_Output *output, Ecore_Drm2_Relative_Mode mode)
{
   EINA_SAFETY_ON_NULL_RETURN(output);
   output->relative.mode = mode;
}

EAPI Ecore_Drm2_Relative_Mode
ecore_drm2_output_relative_mode_get(Ecore_Drm2_Output *output)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(output, ECORE_DRM2_RELATIVE_MODE_UNKNOWN);
   return output->relative.mode;
}

EAPI void
ecore_drm2_output_relative_to_set(Ecore_Drm2_Output *output, const char *relative)
{
   EINA_SAFETY_ON_NULL_RETURN(output);
   eina_stringshare_replace(&output->relative.to, relative);
}

EAPI const char *
ecore_drm2_output_relative_to_get(Ecore_Drm2_Output *output)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(output, NULL);
   return output->relative.to;
}

EAPI Eina_Bool
ecore_drm2_output_background_color_set(Ecore_Drm2_Output *output, uint64_t r, uint64_t g, uint64_t b, uint64_t a)
{
   Ecore_Drm2_Crtc_State *cstate;

   EINA_SAFETY_ON_NULL_RETURN_VAL(output, EINA_FALSE);
   EINA_SAFETY_ON_NULL_RETURN_VAL(output->crtc_state, EINA_FALSE);

   cstate = output->crtc_state;
   if (cstate->background.id)
     {
        cstate->background.value = (a << 48 | b << 32 | g << 16 | r);
        return _fb_atomic_flip_test(output);
     }

   return EINA_FALSE;
}
