/*
 * Copyright © 2023 Codethink Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "kms.h"
#include "image.h"
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <xf86drm.h>
#include <xf86drmMode.h>

static char card[20] = "/dev/dri/";
static int rgb = 0;

void kms_list_crtcs(char *reply) {
  int len = strlen(reply);

  int fd = open(card, O_RDONLY);
  if (fd < 0) {
    fprintf(stderr, "Error opening %s: %s\n", card, strerror(errno));
    return;
  }

  drmModeRes *res = drmModeGetResources(fd);
  if (!res) {
    fprintf(stderr, "Error getting display config: %s\n", strerror(errno));
    fprintf(stderr, "Is DRM device set correctly?\n");
    close(fd);
    return;
  }

  for (int i = 0; i < res->count_crtcs; ++i) {
    drmModeCrtc *crtc = drmModeGetCrtc(fd, res->crtcs[i]);
    if (!crtc) {
      fprintf(stderr, "Error getting CRTC '%d': %s\n", res->crtcs[i],
              strerror(errno));
      continue;
    }

    len +=
        sprintf(reply + len, "CRTC: ID=%" PRIu32 ", mode_valid=%" PRIu32 "\n",
                crtc->crtc_id, crtc->mode_valid);

    drmModeFreeCrtc(crtc);
  }

  drmModeFreeResources(res);

  close(fd);
}

int kms_grab_fb(qad_screen_buffer_t *screen_buffer, int screen) {
  int fd, fb_size;
  drmModeCrtc *crtc;
  drmModeFB *fb;
  void *ptr;
  struct drm_mode_map_dumb dumb_map;

  fd = open(card, O_RDWR | O_CLOEXEC);

  crtc = drmModeGetCrtc(fd, screen);
  if (!crtc) {
    fprintf(stderr, "Error getting CRTC '%d': %s\n", screen, strerror(errno));
    return -1;
  }

  fb = drmModeGetFB(fd, crtc->buffer_id);
  if (!fb) {
    fprintf(stderr, "Error getting framebuffer '%" PRIu32 "': %s\n",
            crtc->buffer_id, strerror(errno));
    drmModeFreeCrtc(crtc);
    return -1;
  }

  fb_size = fb->pitch * fb->height;
  dumb_map.handle = fb->handle;
  dumb_map.offset = 0;

  drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &dumb_map);
  ptr = mmap(0, fb_size, PROT_READ, MAP_SHARED, fd, dumb_map.offset);

  write_png(ptr, fb->width, fb->height, fb->pitch, fb->bpp, rgb, screen_buffer);

  munmap(ptr, fb_size);
  drmModeFreeFB(fb);
  drmModeFreeCrtc(crtc);
  close(fd);
  screen_buffer->type = BUFFER_TYPE_PNG;
  return 0;
}

int kms_create_backend(qad_backend_screen_t *backend,
                       const char *kms_backend_card, const int kms_format_rgb) {
  strcat(card, kms_backend_card);
  rgb = kms_format_rgb;
  // Try to open the desired card to check user provides valid CLI option for
  // kms_backend_card
  int fd = open(card, O_RDWR | O_CLOEXEC);
  if (fd == -1) {
    fprintf(stderr, "Failed to open %s!\n", card);
    return -1;
  }
  close(fd);
  backend->list_fbs = kms_list_crtcs;
  backend->grab_fb = kms_grab_fb;
  return 0;
}
