#include "git_repo.h"
#include "git_repo_blob.h"

#include "../templ/duc_git_repo_blob.h"

static orb_rc_t init(orb_module_t *self) {
  duc_git_repo_blob_t *data = self->data;
  git_repo_blob_param_list_t *param_list = self->param_list;
  if (param_list) {
    data->repo = param_list->repo;
    data->is_markdown = param_list->is_markdown;
    data->is_raw = param_list->is_raw;
  }
  return ORB_OK;
}

static int _fetch_tree_cb(const char *root, const git_tree_entry *entry_handle, void *data) {
  git_repo_blob_tree_cb_t *handle = data;
  const char *name = git_tree_entry_name(entry_handle);
  git_filemode_t mode_handle = git_tree_entry_filemode(entry_handle);
  if (mode_handle != GIT_FILEMODE_TREE) {
    const git_oid *oid = git_tree_entry_id(entry_handle);
    size_t file_path_len = strlen(root) + strlen(name);
    char file_path[file_path_len + 1];
    snprintf(file_path, sizeof(file_path), "%s%s", root, name);
    if (strcmp(handle->file_path, file_path) == 0) {
      git_oid_cpy(handle->blob_oid, oid);
      return -1;
    }
  }
  return 0;
}

static orb_rc_t fetch(orb_module_t *self) {
  duc_git_repo_blob_t *data = self->data;
  if (!data->repo) return ORB_FATAL(1);
  git_oid blob_oid;
  if (data->repo->flag_oid) {
    if (git_oid_fromstr(&blob_oid, data->repo->flag_oid) != 0) return ORB_FATAL(2);
  }
  if (data->repo->flag_path) {
    char *ref_oid = NULL;
    if (data->repo->ref_head) ref_oid = data->repo->ref_head->oid;
    if (data->repo->ref) ref_oid = data->repo->ref->oid;
    if (!ref_oid) return ORB_ERR(2);
    const char *spec_suffix = "^{commit}";
    size_t spec_len = strlen(ref_oid) + strlen(spec_suffix) + 1;
    char spec[spec_len];
    snprintf(spec, spec_len, "%s%s", ref_oid, spec_suffix);
    git_object *obj = NULL;
    if (git_revparse_single(&obj, data->repo->handle, spec) == 0) {
      git_commit *commit_handle = (git_commit *) obj;
      git_tree *tree_handle = NULL;
      if (git_commit_tree(&tree_handle, commit_handle) == 0) {
        git_tree_walk(tree_handle, GIT_TREEWALK_PRE, _fetch_tree_cb, &((git_repo_blob_tree_cb_t) {
          .blob_oid = &blob_oid,
          .file_path = data->repo->flag_path,
        }));
        git_tree_free(tree_handle);
      }
      git_object_free(obj);
    }
    if (git_oid_is_zero(&blob_oid)) return ORB_FATAL(3);
  }
  git_blob *blob_handle = NULL;
  if (git_blob_lookup(&blob_handle, data->repo->handle, &blob_oid) != 0) return ORB_FATAL(4);
  data->blob = calloc(1, sizeof(git_repo_blob_t));
  git_repo_blob_t *blob = data->blob;
  char oid_str[GIT_OID_HEXSZ + 1];
  git_oid_tostr(oid_str, sizeof(oid_str), &blob_oid);
  blob->oid = strdup(oid_str);
  blob->name = NULL;
  size_t size = (size_t) git_blob_rawsize(blob_handle);
  const char *content = git_blob_rawcontent(blob_handle);
  if (content) {
    blob->content = malloc(size + 1);
    memcpy(blob->content, content, size);
    blob->content[size] = '\0';
    blob->size = size;
  }
  blob->is_binary = git_blob_is_binary(blob_handle);
  git_blob_free(blob_handle);
  return ORB_OK;
}

static orb_rc_t render(orb_module_t *self, void *out) {
  return ORB_ERR(duc_git_repo_blob_render(out, (duc_git_repo_blob_t *) self->data));
}

static void cleanup(orb_module_t *self) {
  duc_git_repo_blob_t *data = self->data;
  if (data->blob) {
    free(data->blob->oid);
    if (data->blob->name) free(data->blob->name);
    if (data->blob->content) free(data->blob->content);
    free(data->blob);
    data->blob = NULL;
  }
}

const orb_module_type_t GIT_REPO_BLOB_TYPE = {
  .size = sizeof(duc_git_repo_blob_t),
  .init = init,
  .fetch = fetch,
  .render = render,
  .cleanup = cleanup,
};
