/*
 * 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 <fcntl.h>
#include <inttypes.h>
#include <png.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;

typedef struct {
  unsigned char *buffer;
  int size;
} png_buffer_t;

void write_png_func(png_structp png_ptr, png_bytep data, png_size_t length) {
  png_buffer_t *png_buffer = (png_buffer_t *)png_get_io_ptr(png_ptr);
  size_t nsize = png_buffer->size + length;

  if (png_buffer->buffer)
    png_buffer->buffer = realloc(png_buffer->buffer, nsize);
  else
    png_buffer->buffer = malloc(nsize);

  memcpy(png_buffer->buffer + png_buffer->size, data, length);
  png_buffer->size += length;
}

void write_png(void *ptr, png_buffer_t *png_buffer, int width, int height,
               int pitch, int bpp) {
  png_structp png_ptr;
  png_infop png_info_ptr;
  jmp_buf png_jmpbuf;
  int colour_type = PNG_COLOR_TYPE_RGB;

  png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  png_info_ptr = png_create_info_struct(png_ptr);

  setjmp(png_jmpbuf);

  png_set_compression_level(png_ptr, 1);

  png_set_write_fn(png_ptr, png_buffer, write_png_func, NULL);
  png_set_IHDR(png_ptr, png_info_ptr, width, height, 8, colour_type,
               PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
               PNG_FILTER_TYPE_DEFAULT);
  png_write_info(png_ptr, png_info_ptr);

  // TODO: Big assumption
  if (bpp == 32) {
    if (!rgb)
      png_set_bgr(png_ptr);
    png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
  }

  for (int j = 0; j < height; ++j) {
    png_const_bytep pointer = (png_const_bytep)ptr;
    pointer += j * pitch;
    setjmp(png_jmpbuf);
    png_write_row(png_ptr, pointer);
  }

  setjmp(png_jmpbuf);
  png_write_end(png_ptr, NULL);
  png_destroy_write_struct(&png_ptr, &png_info_ptr);
}

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\n: %s", strerror(errno));
    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);
}

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

  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 NULL;
  }

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

  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);

  png_buffer.buffer = NULL;
  png_buffer.size = 0;
  write_png(ptr, &png_buffer, fb->width, fb->height, fb->pitch, fb->bpp);

  munmap(ptr, fb_size);
  drmModeFreeFB(fb);
  drmModeFreeCrtc(crtc);
  close(fd);
  qad_screen_buffer_t *buffer = calloc(1, sizeof(qad_screen_buffer_t));
  buffer->buffer = png_buffer.buffer;
  buffer->buffer_size = png_buffer.size;
  return buffer;
}

qad_backend_screen_t *kms_create_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 NULL;
  }
  close(fd);
  qad_backend_screen_t *backend = calloc(1, sizeof(qad_backend_screen_t));
  backend->list_fbs = kms_list_crtcs;
  backend->grab_fb = kms_grab_fb;
  return backend;
}
