// Copyright 2011-2020 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

#include "memory.h"
#include "model.h"
#include "statement.h"

#include "serd/caret.h"
#include "serd/event.h"
#include "serd/inserter.h"
#include "serd/log.h"
#include "serd/model.h"
#include "serd/node.h"
#include "serd/nodes.h"
#include "serd/sink.h"
#include "serd/statement.h"
#include "serd/status.h"
#include "serd/uri.h"
#include "serd/world.h"

#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>

typedef struct {
  SerdModel* model;
  SerdNode*  default_graph;
} SerdInserterData;

static bool
can_insert(SerdWorld* const world, const SerdNode* const node)
{
  if (node) {
    switch (serd_node_type(node)) {
    case SERD_LITERAL:
      return can_insert(world, serd_node_datatype(node));

    case SERD_URI:
      if (!serd_uri_string_has_scheme(serd_node_string(node))) {
        serd_logf(world,
                  SERD_LOG_LEVEL_ERROR,
                  "attempt to insert relative URI <%s> into model",
                  serd_node_string(node));
        return false;
      }
      break;

    case SERD_BLANK:
    case SERD_VARIABLE:
      break;
    }
  }

  return true;
}

static SerdStatus
serd_inserter_on_statement(SerdInserterData* const    data,
                           const SerdStatementFlags   flags,
                           const SerdStatement* const statement)
{
  (void)flags;

  SerdModel* const model = data->model;
  SerdWorld* const world = model->world;

  // Check that every node is expanded so it is context-free
  for (unsigned i = 0; i < 4; ++i) {
    if (!can_insert(world, serd_statement_node(statement, (SerdField)i))) {
      return SERD_BAD_DATA;
    }
  }

  const SerdNode* const s =
    serd_nodes_intern(model->nodes, serd_statement_subject(statement));

  const SerdNode* const p =
    serd_nodes_intern(model->nodes, serd_statement_predicate(statement));

  const SerdNode* const o =
    serd_nodes_intern(model->nodes, serd_statement_object(statement));

  const SerdNode* const g = serd_nodes_intern(
    model->nodes,
    serd_statement_graph(statement) ? serd_statement_graph(statement)
                                    : data->default_graph);

  const SerdCaret* const caret =
    (data->model->flags & SERD_STORE_CARETS) ? statement->caret : NULL;

  const SerdStatus st =
    serd_model_add_with_caret(data->model, s, p, o, g, caret);

  return st > SERD_FAILURE ? st : SERD_SUCCESS;
}

static SerdStatus
serd_inserter_on_event(void* const handle, const SerdEvent* const event)
{
  SerdInserterData* const data = (SerdInserterData*)handle;

  if (event->type == SERD_STATEMENT) {
    return serd_inserter_on_statement(
      data, event->statement.flags, event->statement.statement);
  }

  return SERD_SUCCESS;
}

static SerdInserterData*
serd_inserter_data_new(SerdModel* const      model,
                       const SerdNode* const default_graph)
{
  SerdInserterData* const data =
    (SerdInserterData*)serd_wcalloc(model->world, 1, sizeof(SerdInserterData));

  if (data) {
    data->model         = model;
    data->default_graph = serd_node_copy(model->allocator, default_graph);
  }

  return data;
}

static void
serd_inserter_data_free(void* const ptr)
{
  SerdInserterData* const data = (SerdInserterData*)ptr;
  serd_node_free(data->model->allocator, data->default_graph);
  serd_wfree(data->model->world, data);
}

SerdSink*
serd_inserter_new(SerdModel* const model, const SerdNode* const default_graph)
{
  assert(model);

  SerdEventFunc           func = serd_inserter_on_event;
  SerdInserterData* const data = serd_inserter_data_new(model, default_graph);

  return data ? serd_sink_new(serd_world_allocator(model->world),
                              data,
                              func,
                              (SerdFreeFunc)serd_inserter_data_free)
              : NULL;
}