/*
 * Copyright 2024 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "gdraw_command_image.h"
#include "gdraw_command_private.h"

#include "applib/applib_malloc.auto.h"
#include "applib/applib_resource_private.h"
#include "syscall/syscall.h"

GDrawCommandImage *gdraw_command_image_create_with_resource(uint32_t resource_id) {
  ResAppNum app_num = sys_get_current_resource_num();

  return gdraw_command_image_create_with_resource_system(app_num, resource_id);
}

GDrawCommandImage *gdraw_command_image_create_with_resource_system(ResAppNum app_num,
                                                                   uint32_t resource_id) {
  uint32_t data_size;
  if (!gdraw_command_resource_is_valid(app_num, resource_id, PDCI_SIGNATURE, &data_size)) {
    return NULL;
  }

  GDrawCommandImage *draw_command_image = applib_resource_mmap_or_load(app_num, resource_id,
                                                                       PDCI_DATA_OFFSET, data_size,
                                                                       false);

  // Validate the loaded command image
  if (!gdraw_command_image_validate(draw_command_image, data_size)) {
    gdraw_command_image_destroy(draw_command_image);
    return NULL;
  }

  return draw_command_image;
}

bool gdraw_command_image_copy(void *buffer, size_t buffer_length, GDrawCommandImage *src) {
  size_t src_size = gdraw_command_image_get_data_size(src);
  if (buffer_length < src_size) {
    return false;
  }

  memcpy(buffer, src, src_size);
  return true;
}

GDrawCommandImage *gdraw_command_image_clone(GDrawCommandImage *image) {
  if (!image) {
    return NULL;
  }

  // potentially extracting into a generic task_ptrdup(void *, size_t)
  size_t size = gdraw_command_image_get_data_size(image);
  GDrawCommandImage *result = applib_malloc(size);
  if (result) {
    memcpy(result, image, size);
  }

  return result;
}

void gdraw_command_image_destroy(GDrawCommandImage *image) {
  applib_resource_munmap_or_free(image);
}

bool gdraw_command_image_validate(GDrawCommandImage *image, size_t size) {
  if (!image ||
      (size < sizeof(GDrawCommandImage)) ||
      (image->version > GDRAW_COMMAND_VERSION) ||
      !gdraw_command_list_validate(&image->command_list, size - (sizeof(GDrawCommandImage) -
          sizeof(GDrawCommandList)))) {
    return false;
  }
  uint8_t *end = (uint8_t *)image + size;

  return (end == gdraw_command_list_iterate_private(&image->command_list, NULL, NULL));
}

void gdraw_command_image_draw(GContext *ctx, GDrawCommandImage *image, GPoint offset) {
  gdraw_command_image_draw_processed(ctx, image, offset, NULL);
}

void gdraw_command_image_draw_processed(GContext *ctx, GDrawCommandImage *image, GPoint offset,
                                        GDrawCommandProcessor *processor) {
  if (!ctx || !image) {
    return;
  }

  // Offset graphics context drawing box origin by specified amount
  graphics_context_move_draw_box(ctx, offset);

  gdraw_command_list_draw_processed(ctx, &image->command_list, processor);

  // Offset graphics context drawing box back to previous origin
  graphics_context_move_draw_box(ctx, GPoint(-offset.x, -offset.y));
}

size_t gdraw_command_image_get_data_size(GDrawCommandImage *image) {
  if (!image) {
    return 0;
  }

  return sizeof(GDrawCommandImage) - sizeof(GDrawCommandList)
      + gdraw_command_list_get_data_size(&image->command_list);
}

GSize gdraw_command_image_get_bounds_size(GDrawCommandImage *image) {
  if (!image) {
    return GSizeZero;
  }

  return image->size;
}

void gdraw_command_image_set_bounds_size(GDrawCommandImage *image, GSize size) {
  if (!image) {
    return;
  }

  image->size = size;
}

GDrawCommandList *gdraw_command_image_get_command_list(GDrawCommandImage *image) {
  if (!image) {
    return NULL;
  }

  return &image->command_list;
}
