#include "ejdb2_internal.h"

static iwrc _jbi_consume_eq(struct _JBEXEC *ctx, JQVAL *jqval, JB_SCAN_CONSUMER consumer) {
  iwrc rc;
  bool matched;
  IWKV_cursor cur;
  char numbuf[JBNUMBUF_SIZE];

  int64_t step = 1;
  struct _JBMIDX *midx = &ctx->midx;
  JBIDX idx = midx->idx;
  IWKV_cursor_op cursor_reverse_step = IWKV_CURSOR_NEXT;
  midx->cursor_step = IWKV_CURSOR_PREV;

  IWKV_val key;
  jbi_jqval_fill_ikey(idx, jqval, &key, numbuf);
  key.compound = INT64_MIN;
  if (!key.size) {
    return consumer(ctx, 0, 0, 0, 0, 0);
  }
  rc = iwkv_cursor_open(idx->idb, &cur, IWKV_CURSOR_GE, &key);
  if (rc == IWKV_ERROR_NOTFOUND) {
    return consumer(ctx, 0, 0, 0, 0, 0);
  } else {
    RCRET(rc);
  }

  do {
    if (step > 0) {
      --step;
    } else if (step < 0) {
      ++step;
    }
    if (!step) {
      int64_t id;
      RCC(rc, finish, iwkv_cursor_is_matched_key(cur, &key, &matched, &id));
      if (!matched) {
        break;
      }
      step = 1;
      RCC(rc, finish, consumer(ctx, 0, id, &step, &matched, 0));
    }
  } while (step && !(rc = iwkv_cursor_to(cur, step > 0 ? midx->cursor_step : cursor_reverse_step)));

finish:
  if (rc == IWKV_ERROR_NOTFOUND) {
    rc = 0;
  }
  if (cur) {
    iwkv_cursor_close(&cur);
  }
  return consumer(ctx, 0, 0, 0, 0, rc);
}

static int _jbi_cmp_jqval(const void *v1, const void *v2) {
  iwrc rc;
  const JQVAL *jqv1 = v1;
  const JQVAL *jqv2 = v2;
  return jql_cmp_jqval_pair(jqv1, jqv2, &rc);
}

static iwrc _jbi_consume_in_node(struct _JBEXEC *ctx, JQVAL *jqval, JB_SCAN_CONSUMER consumer) {
  int i;
  int64_t id;
  bool matched;
  char jqvarrbuf[512];
  char numbuf[JBNUMBUF_SIZE];

  iwrc rc = 0;
  int64_t step = 1;
  IWKV_cursor cur = 0;
  struct _JBMIDX *midx = &ctx->midx;
  JBIDX idx = midx->idx;
  IWKV_val key = { .compound = INT64_MIN };
  JBL_NODE nv = jqval->vnode->child;

  for (i = 0; nv; nv = nv->next) {
    if ((nv->type >= JBV_BOOL) && (nv->type <= JBV_STR)) {
      ++i;
    }
  }
  if (i == 0) {
    return consumer(ctx, 0, 0, 0, 0, 0);
  }

  JQVAL *jqvarr = (i * sizeof(*jqvarr)) <= sizeof(jqvarrbuf)
                  ? jqvarrbuf : malloc(i * sizeof(*jqvarr));
  if (!jqvarr) {
    return iwrc_set_errno(IW_ERROR_ALLOC, errno);
  }
  for (i = 0, nv = jqval->vnode->child; nv; nv = nv->next) {
    if ((nv->type >= JBV_BOOL) && (nv->type <= JBV_STR)) {
      JQVAL jqv;
      jql_node_to_jqval(nv, &jqv);
      memcpy(&jqvarr[i++], &jqv, sizeof(jqv));
    }
  }
  // Sort jqvarr according to index order, lowest first (asc)
  qsort(jqvarr, i, sizeof(jqvarr[0]), _jbi_cmp_jqval);

  for (int c = 0; c < i && !rc; ++c) {
    JQVAL *jqv = &jqvarr[c];
    jbi_jqval_fill_ikey(idx, jqv, &key, numbuf);
    if (cur) {
      iwkv_cursor_close(&cur);
    }
    RCC(rc, finish, iwkv_cursor_open(idx->idb, &cur, IWKV_CURSOR_GE, &key));
    do {
      if (step > 0) {
        --step;
      } else if (step < 0) {
        ++step;
      }
      if (!step) {
        RCC(rc, finish, iwkv_cursor_is_matched_key(cur, &key, &matched, &id));
        if (!matched) {
          break;
        }
        step = 1;
        RCC(rc, finish, consumer(ctx, 0, id, &step, &matched, 0));
      }
    } while (step && !(rc = iwkv_cursor_to(cur, IWKV_CURSOR_PREV))); // !!! only one direction
  }

finish:
  if (rc == IWKV_ERROR_NOTFOUND) {
    rc = 0;
  }
  if (cur) {
    iwkv_cursor_close(&cur);
  }
  if ((char*) jqvarr != jqvarrbuf) {
    free(jqvarr);
  }
  return consumer(ctx, 0, 0, 0, 0, rc);
}

static iwrc _jbi_consume_scan(struct _JBEXEC *ctx, JQVAL *jqval, JB_SCAN_CONSUMER consumer) {
  size_t sz;
  IWKV_cursor cur;
  char numbuf[JBNUMBUF_SIZE];

  int64_t step = 1, prev_id = 0;
  struct _JBMIDX *midx = &ctx->midx;
  JBIDX idx = midx->idx;
  jqp_op_t expr1_op = midx->expr1->op->value;

  IWKV_val key;
  jbi_jqval_fill_ikey(idx, jqval, &key, numbuf);
  if (!key.size) {
    return consumer(ctx, 0, 0, 0, 0, 0);
  }
  key.compound = (midx->cursor_step == IWKV_CURSOR_PREV) ? INT64_MIN : INT64_MAX;

  iwrc rc = iwkv_cursor_open(idx->idb, &cur, midx->cursor_init, &key);
  if ((rc == IWKV_ERROR_NOTFOUND) && ((expr1_op == JQP_OP_LT) || (expr1_op == JQP_OP_LTE))) {
    iwkv_cursor_close(&cur);
    key.compound = INT64_MAX;
    midx->cursor_init = IWKV_CURSOR_BEFORE_FIRST;
    midx->cursor_step = IWKV_CURSOR_NEXT;
    RCC(rc, finish, iwkv_cursor_open(idx->idb, &cur, midx->cursor_init, 0));
    if (!midx->expr2) { // Fail fast
      midx->expr2 = midx->expr1;
    }
  } else if (rc) {
    goto finish;
  }

  if (midx->cursor_init < IWKV_CURSOR_NEXT) { // IWKV_CURSOR_BEFORE_FIRST || IWKV_CURSOR_AFTER_LAST
    RCC(rc, finish, iwkv_cursor_to(cur, midx->cursor_step));
  }

  IWKV_cursor_op cursor_reverse_step = (midx->cursor_step == IWKV_CURSOR_PREV)
                                       ? IWKV_CURSOR_NEXT : IWKV_CURSOR_PREV;
  do {
    if (step > 0) {
      --step;
    } else if (step < 0) {
      ++step;
    }
    if (!step) {
      int64_t id;
      bool matched = false;
      RCC(rc, finish, iwkv_cursor_copy_key(cur, 0, 0, &sz, &id));
      if (  midx->expr2
         && !midx->expr2->prematched
         && !jbi_node_expr_matched(ctx->ux->q->aux, midx->idx, cur, midx->expr2, &rc)) {
        break;
      }
      if (  (expr1_op == JQP_OP_PREFIX)
         && !jbi_node_expr_matched(ctx->ux->q->aux, midx->idx, cur, midx->expr1, &rc)) {
        break;
      }
      RCGO(rc, finish);
      step = 1;
      if (id != prev_id) {
        RCC(rc, finish, consumer(ctx, 0, id, &step, &matched, 0));
        if (!midx->expr1->prematched && matched && (expr1_op != JQP_OP_PREFIX)) {
          // Further scan will always match main index expression
          midx->expr1->prematched = true;
        }
        prev_id = step < 1 ? 0 : id;
      }
    }
  } while (step && !(rc = iwkv_cursor_to(cur, step > 0 ? midx->cursor_step : cursor_reverse_step)));

finish:
  if (rc == IWKV_ERROR_NOTFOUND) {
    rc = 0;
  }
  if (cur) {
    iwkv_cursor_close(&cur);
  }
  return consumer(ctx, 0, 0, 0, 0, rc);
}

static iwrc _jbi_consume_noxpr_scan(struct _JBEXEC *ctx, JB_SCAN_CONSUMER consumer) {
  iwrc rc;
  size_t sz;
  IWKV_cursor cur;
  int64_t step = 1, prev_id = 0;
  struct _JBMIDX *midx = &ctx->midx;
  IWKV_cursor_op cursor_reverse_step = (midx->cursor_step == IWKV_CURSOR_PREV)
                                       ? IWKV_CURSOR_NEXT : IWKV_CURSOR_PREV;

  RCC(rc, finish, iwkv_cursor_open(midx->idx->idb, &cur, midx->cursor_init, 0));
  if (midx->cursor_init < IWKV_CURSOR_NEXT) { // IWKV_CURSOR_BEFORE_FIRST || IWKV_CURSOR_AFTER_LAST
    RCC(rc, finish, iwkv_cursor_to(cur, midx->cursor_step));
  }
  do {
    if (step > 0) {
      --step;
    } else if (step < 0) {
      ++step;
    }
    if (!step) {
      int64_t id;
      bool matched;
      RCC(rc, finish, iwkv_cursor_copy_key(cur, 0, 0, &sz, &id));
      step = 1;
      if (id != prev_id) {
        RCC(rc, finish, consumer(ctx, 0, id, &step, &matched, 0));
        prev_id = step < 1 ? 0 : id;
      }
    }
  } while (step && !(rc = iwkv_cursor_to(cur, step > 0 ? midx->cursor_step : cursor_reverse_step)));

finish:
  if (rc == IWKV_ERROR_NOTFOUND) {
    rc = 0;
  }
  if (cur) {
    iwkv_cursor_close(&cur);
  }
  return consumer(ctx, 0, 0, 0, 0, rc);
}

iwrc jbi_dup_scanner(struct _JBEXEC *ctx, JB_SCAN_CONSUMER consumer) {
  iwrc rc;
  struct _JBMIDX *midx = &ctx->midx;
  if (!midx->expr1) {
    return _jbi_consume_noxpr_scan(ctx, consumer);
  }
  JQP_QUERY *qp = ctx->ux->q->qp;
  JQVAL *jqval = jql_unit_to_jqval(qp->aux, midx->expr1->right, &rc);
  RCRET(rc);
  switch (midx->expr1->op->value) {
    case JQP_OP_EQ:
      return _jbi_consume_eq(ctx, jqval, consumer);
    case JQP_OP_IN:
      if (jqval->type == JQVAL_JBLNODE) {
        return _jbi_consume_in_node(ctx, jqval, consumer);
      } else {
        iwlog_ecode_error3(IW_ERROR_ASSERTION);
        return IW_ERROR_ASSERTION;
      }
      break;
    default:
      break;
  }

  if ((midx->expr1->op->value == JQP_OP_GT) && (jqval->type == JQVAL_I64)) {
    JQVAL mjqv;
    memcpy(&mjqv, jqval, sizeof(*jqval));
    mjqv.vi64 = mjqv.vi64 + 1; // Because for index scan we use `IWKV_CURSOR_GE`
    return _jbi_consume_scan(ctx, &mjqv, consumer);
  } else {
    return _jbi_consume_scan(ctx, jqval, consumer);
  }
}
