/*
 * Copyright © 2022 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 <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <unistd.h>
#include <wayland-client.h>

#include "ilm.h"
#include "image.h"
#include "ivi-wm-client-protocol.h"

static void output_handle_geometry(void *data, struct wl_output *wl_output,
                                   int x, int y, int physical_width,
                                   int physical_height, int subpixel,
                                   const char *make, const char *model,
                                   int transform) {
  struct wayland_screen *output;

  output = wl_output_get_user_data(wl_output);

  if (wl_output == output->output) {
    output->offset_x = x;
    output->offset_y = y;
  }
}

static void output_handle_mode(void *data, struct wl_output *wl_output,
                               uint32_t flags, int width, int height,
                               int refresh) {
  struct wayland_screen *output;

  output = wl_output_get_user_data(wl_output);

  if (wl_output == output->output && (flags & WL_OUTPUT_MODE_CURRENT)) {
    output->width = width;
    output->height = height;
  }
}

static void output_handle_done(void *data, struct wl_output *wl_output) {}

static void output_handle_scale(void *data, struct wl_output *wl_output,
                                int32_t scale) {}

static const struct wl_output_listener output_listener = {
    output_handle_geometry,
    output_handle_mode,
    output_handle_done,
    output_handle_scale,
};

static void wm_screen_listener_screen_id(void *data,
                                         struct ivi_wm_screen *controller,
                                         uint32_t screen_id) {
  struct wayland_screen *output = data;

  output->id_screen = screen_id;
}

static void wm_screen_listener_layer_added(void *data,
                                           struct ivi_wm_screen *controller,
                                           uint32_t layer_id) {}

static void wm_screen_listener_connector_name(void *data,
                                              struct ivi_wm_screen *controller,
                                              const char *connector_name) {}

static void wm_screen_listener_error(void *data,
                                     struct ivi_wm_screen *controller,
                                     uint32_t code, const char *message) {}

static struct ivi_wm_screen_listener wm_screen_listener = {
    wm_screen_listener_screen_id,
    wm_screen_listener_layer_added,
    wm_screen_listener_connector_name,
    wm_screen_listener_error,
};

static void global_registry_handler(void *data, struct wl_registry *registry,
                                    uint32_t id, const char *interface,
                                    uint32_t version) {
  struct wayland_data *globals = data;

  if (strcmp(interface, "wl_output") == 0) {
    struct wayland_screen *output;
    output = calloc(1, sizeof(struct wayland_screen));
    output->output = wl_registry_bind(registry, id, &wl_output_interface, 1);
    wl_output_add_listener(output->output, &output_listener, output);
    if (globals->wm) {
      output->wm_screen = ivi_wm_create_screen(globals->wm, output->output);
      ivi_wm_screen_add_listener(output->wm_screen, &wm_screen_listener,
                                 output);
    }
    wl_list_insert(&globals->output_list, &output->link);
  } else if (strcmp(interface, "ivi_wm") == 0) {
    globals->wm = wl_registry_bind(registry, id, &ivi_wm_interface, 1);
  }
}

static void global_registry_remover(void *data, struct wl_registry *registry,
                                    uint32_t id) {
  printf("Got a registry losing event for %d\n", id);
}

static const struct wl_registry_listener registry_listener = {
    global_registry_handler,
    global_registry_remover,
};

static void ivi_screenshot_done(void *data,
                                struct ivi_screenshot *ivi_screenshot,
                                int32_t fd, int32_t width, int32_t height,
                                int32_t stride, uint32_t format,
                                uint32_t timestamp) {
  struct screenshot *screenshot = data;
  unsigned char *buffer;
  int32_t image_size = 0;
  int32_t row = 0;
  int32_t col = 0;
  int32_t image_offset = 0;
  int32_t offset = 0;
  int32_t i = 0;
  int32_t j = 0;
  int bytes_per_pixel;
  int flip_order;
  int has_alpha;

  screenshot->done = 1;
  ivi_screenshot_destroy(ivi_screenshot);

  switch (format) {
  case WL_SHM_FORMAT_ARGB8888:
    flip_order = 1;
    has_alpha = 1;
    break;
  case WL_SHM_FORMAT_XRGB8888:
    flip_order = 1;
    has_alpha = 0;
    break;
  case WL_SHM_FORMAT_ABGR8888:
    flip_order = 0;
    has_alpha = 1;
    break;
  case WL_SHM_FORMAT_XBGR8888:
    flip_order = 0;
    has_alpha = 0;
    break;
  default:
    fprintf(stderr, "unsupported pixelformat 0x%x\n", format);
    return;
  }

  bytes_per_pixel = has_alpha ? 4 : 3;
  image_size = stride * height;

  buffer = mmap(NULL, image_size, PROT_READ, MAP_SHARED, fd, 0);
  close(fd);

  if (buffer == MAP_FAILED) {
    fprintf(stderr, "failed to mmap screenshot file: %m\n");
    return;
  }

  screenshot->data = malloc(image_size);
  if (screenshot->data == NULL) {
    fprintf(stderr, "failed to allocate %d bytes for image buffer: %m\n",
            image_size);
    goto done;
  }

  // Store the image in image_buffer in the follwing order B, G, R, [A](B at the
  // lowest address)
  for (row = 0; row < height; ++row) {
    for (col = 0; col < width; ++col) {
      offset = (height - row - 1) * width + col;
      uint32_t pixel = htonl(((uint32_t *)buffer)[offset]);
      char *pixel_p = (char *)&pixel;
      image_offset = row * stride + col * bytes_per_pixel;
      for (i = 0; i < 3; ++i) {
        j = flip_order ? 2 - i : i;
        screenshot->data[image_offset + i] = pixel_p[1 + j];
      }
      if (has_alpha) {
        screenshot->data[image_offset + 3] = pixel_p[0];
      }
    }
  }
  screenshot->height = height;
  screenshot->width = width;
  screenshot->stride = stride;
done:
  munmap(buffer, image_size);
}

static void ivi_screenshot_error(void *data,
                                 struct ivi_screenshot *ivi_screenshot,
                                 uint32_t error, const char *message) {
  struct screenshot *screenshot = data;
  ivi_screenshot_destroy(ivi_screenshot);
  fprintf(stderr, "screenshot failed, error 0x%x: %s\n", error, message);
  screenshot->done = 1;
}

static struct ivi_screenshot_listener screenshot_listener = {
    ivi_screenshot_done,
    ivi_screenshot_error,
};

struct wayland_data globals = {0};

// Grab frame buffer from weston
int ilm_grab_fb(qad_screen_buffer_t *buffer, int screen) {
  struct wayland_screen *output, *chosen_screen;

  chosen_screen = NULL;
  wl_list_for_each(output, &globals.output_list, link) {
    if (screen == output->id_screen) {
      chosen_screen = output;
      break;
    }
  }
  if (chosen_screen == NULL) {
    printf("Failed to find screen with ID %d\n", screen);
    return -1;
  }

  // Grab screenshot of individual screens
  struct ivi_screenshot *scrshot =
      ivi_wm_screen_screenshot(chosen_screen->wm_screen);
  if (!scrshot) {
    return -1;
  }
  struct screenshot screenshot = {0};
  ivi_screenshot_add_listener(scrshot, &screenshot_listener, &screenshot);
  int ret;
  screenshot.done = 0;
  do {
    ret = wl_display_roundtrip_queue(globals.display, globals.queue);
  } while ((ret != -1) && !screenshot.done);

  if (screenshot.data == NULL) {
    fprintf(stderr, "Error taking screenshot\n");
    return -1;
  }

  encode_bmp(screenshot.data, screenshot.width, screenshot.height,
             screenshot.stride, buffer);

  return 0;
}

void ilm_list_screens(char *reply) {}

int ilm_create_backend(qad_backend_screen_t *backend) {
  struct wayland_screen *output;
  globals.display = wl_display_connect(NULL);
  if (globals.display == NULL) {
    fprintf(stderr, "failed to create display: %s\n", strerror(errno));
    return -1;
  }
  globals.queue = wl_display_create_queue(globals.display);
  wl_list_init(&globals.output_list);
  globals.registry = wl_display_get_registry(globals.display);
  wl_proxy_set_queue((void *)globals.registry, globals.queue);
  wl_registry_add_listener(globals.registry, &registry_listener, &globals);
  if (wl_display_roundtrip_queue(globals.display, globals.queue) == -1) {
    fprintf(stderr, "Failed to get globals\n");
    wl_display_disconnect(globals.display);
    return -1;
  }

  if (globals.wm == NULL) {
    fprintf(stderr,
            "Compositor does not support ivi_wm or weston_screenshooter\n");
    wl_display_disconnect(globals.display);
    return -1;
  }

  wl_list_for_each(output, &globals.output_list, link) {
    if (!output->wm_screen) {
      output->wm_screen = ivi_wm_create_screen(globals.wm, output->output);
      ivi_wm_screen_add_listener(output->wm_screen, &wm_screen_listener,
                                 output);
    }
  }

  if (wl_display_roundtrip_queue(globals.display, globals.queue) == -1) {
    fprintf(stderr, "Roundtrip failed\n");
    return -1;
  }

  backend->list_fbs = ilm_list_screens;
  backend->grab_fb = ilm_grab_fb;
  return 0;
}

void ilm_destroy_backend() {
  struct wayland_screen *output, *next;
  wl_list_for_each_safe(output, next, &globals.output_list, link) {
    if (output->wm_screen) {
      ivi_wm_screen_destroy(output->wm_screen);
    }
    wl_output_destroy(output->output);
    free(output);
  }
  if (globals.wm) {
    ivi_wm_destroy(globals.wm);
  }
  wl_registry_destroy(globals.registry);
  wl_event_queue_destroy(globals.queue);
  wl_display_disconnect(globals.display);
}
