/*
 * Dynomite - A thin, distributed replication layer for multi non-distributed
 * storages. Copyright (C) 2014 Netflix, Inc.
 */

/*
 * twemproxy - A fast and lightweight proxy for memcached protocol.
 * Copyright (C) 2011 Twitter, Inc.
 *
 * 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 "dyn_token.h"
#include <stddef.h>
#include <stdint.h>
#include "../dyn_array.h"
#include "../dyn_log.h"
#include "../dyn_string.h"
#include "../dyn_util.h"

/* bitsPerDigit radix of 10 multiplied by 1024, rounded up to avoid
 * underallocation */
#define BITS_PER_DIGIT 3402

/* TODO: jeb - this should be 9, but hacked for POC */
#define DIGITS_PER_INT 10

void init_dyn_token(struct dyn_token *token) {
  token->signum = 0;
  token->len = 0;
}

void deinit_dyn_token(struct dyn_token *token) {
  token->signum = 0;
  token->len = 0;
}

// This implementation does not take into account that token's val can be very
// large, larger than an uint32 number
rstatus_t size_dyn_token(struct dyn_token *token, uint32_t token_len) {
  ASSERT(token_len <= 4);
  token->len = token_len;
  token->signum = 0;

  return DN_OK;
}

// This implementation does not take into account that token's val can be very
// large, larger than an uint32 number
rstatus_t copy_dyn_token(const struct dyn_token *src, struct dyn_token *dst) {
  rstatus_t status = size_dyn_token(dst, 1);
  set_int_dyn_token(dst, src->mag[0]);
  return status;
}

// This implementation does not take into account that token's val can be very
// large, larger than an uint32 number
void set_int_dyn_token(struct dyn_token *token, uint32_t val) {
  token->mag[0] = val;
  token->len = 1;
  token->signum = val > 0 ? 1 : 0;
}

void print_dyn_token(struct dyn_token *token, int num_tabs) {
  if (token == NULL) log_debug(LOG_VERB, "Token is null!!!!!");

  if (num_tabs < 0) num_tabs = 0;

  log_debug(LOG_VERB, "%*cToken : %" PRIu32 " %" PRIu32 " %" PRIu32 " ",
            num_tabs, '\t', token->signum, token->mag[0], token->len);
}

static void add_next_word(uint32_t *buf, uint32_t len, uint32_t next_int) {
  uint64_t product = 0;
  uint64_t carry = 0;

  /* magick! */
  uint32_t radix_val = 0x17179149;
  int32_t i;
  for (i = len - 1; i >= 0; i--) {
    product = radix_val * buf[i] + carry;
    buf[i] = (uint32_t)product;
    carry = product >> 32;
  }

  uint64_t sum = buf[len - 1] + next_int;
  buf[len - 1] = (uint32_t)sum;
  carry = sum >> 32;
  for (i = len - 2; i >= 0; i--) {
    sum = buf[i] + carry;
    buf[i] = (uint32_t)sum;
    carry = sum >> 32;
  }
}

rstatus_t parse_dyn_token(uint8_t *start, uint32_t len,
                          struct dyn_token *token) {
  ASSERT(len > 0);
  ASSERT(token != NULL);

  /* TODO-jeb: check for whitespace */
  char sign = '-';
  uint8_t *p = start;
  uint8_t *q = p + len;
  uint32_t digits = len;
  if (p[0] == sign) {
    token->signum = -1;
    p++;
    digits--;
    ASSERT(digits > 0);
  } else if (digits == 1 && p[0] == '0') {
    token->signum = 0;
  } else {
    token->signum = 1;
  }

  uint32_t nwords;
  /* if (digits < 10) { */
  nwords = 1;
  /* } else { */
  /*     uint32_t nbits = ((digits * BITS_PER_DIGIT) >> 10) + 1; */
  /*     nwords = (nbits + 32) >> 5; */
  /* } */

  uint32_t *buf = token->mag;
  token->len = nwords;

  // Process first (potentially short) digit group
  uint32_t first_group_len = digits % DIGITS_PER_INT;
  if (first_group_len == 0) first_group_len = DIGITS_PER_INT;
  buf[nwords - 1] = dn_atoui(p, first_group_len);
  p += first_group_len;

  // Process remaining digit groups
  while (p < q) {
    uint32_t local_int = dn_atoui(p, DIGITS_PER_INT);
    add_next_word(buf, nwords, local_int);
    p += DIGITS_PER_INT;
  }

  return DN_OK;
}

int32_t cmp_dyn_token(const struct dyn_token *t1, const struct dyn_token *t2) {
  ASSERT(t1 != NULL);
  ASSERT(t2 != NULL);

  if (t1->signum == t2->signum) {
    if (t1->signum == 0) {
      return 0;
    }

    if (t1->len == t2->len) {
      uint32_t i;
      for (i = 0; i < t1->len; i++) {
        uint32_t a = t1->mag[i];
        uint32_t b = t2->mag[i];
        if (a != b) {
          return a > b ? 1 : -1;
        }
      }
      return 0;
    }

    return t1->len > t2->len ? 1 : -1;
  }

  return t1->signum > t2->signum ? 1 : -1;
}

/*
 * Does the work of reading an array of chars, and constructing the tokens
 * for the array.
 */
rstatus_t derive_tokens(struct array *tokens, uint8_t *start, uint8_t *end) {
  ASSERT(end > start);
  uint8_t *p = end;
  uint8_t *q;
  while (p >= start) {
    struct dyn_token *token = array_push(tokens);
    ASSERT(token != NULL);
    init_dyn_token(token);

    q = dn_strrchr(p, start, ',');
    if (q == NULL) {
      q = start; /* we're at the beginning of the list */
    } else {
      q++;
    }

    uint32_t len = 0;
    if (p == end) {
      len = (uint32_t)(p - q);
    } else {
      len = (uint32_t)(p - q + 1);
    }

    rstatus_t status = parse_dyn_token(q, len, token);
    if (status != DN_OK) {
      return DN_ERROR;
    }

    p = q - 2;
  }

  return DN_OK;
}

rstatus_t derive_token(struct dyn_token *token, uint8_t *start, uint8_t *end) {
  ASSERT(end > start);
  uint8_t *p = end;
  uint8_t *q;
  ASSERT(token != NULL);
  init_dyn_token(token);

  if (p >= start) {
    q = dn_strrchr(p, start, ',');
    if (q == NULL) {
      q = start; /* we're at the beginning of the list */
    } else {
      q++;
    }

    uint32_t len = 0;
    if (p == end) {
      len = (uint32_t)(p - q);
    } else {
      len = (uint32_t)(p - q + 1);
    }

    rstatus_t status = parse_dyn_token(q, len, token);
    if (status != DN_OK) {
      return DN_ERROR;
    }

    p = q - 2;
  }

  return DN_OK;
}
