// SPDX-FileCopyrightText: 2021 Benjamin Brock
//
// SPDX-License-Identifier: BSD-3-Clause

#pragma once

#include "packing.hpp"
#include "hash_funcs.hpp"

#include <algorithm>
#include <string>
#include <cstring>

std::string rcomplement(std::string str) {
  std::string rstr = std::string(str);
  for (int i = 0; i < str.length(); i++) {
    if (str[i] == 'A') {
      rstr[i] = 'T';
    }
    if (str[i] == 'C') {
      rstr[i] = 'G';
    }
    if (str[i] == 'G') {
      rstr[i] = 'C';
    }
    if (str[i] == 'T') {
      rstr[i] = 'A';
    }
  }
  std::reverse(rstr.begin(), rstr.end());
  return rstr;
}

std::string canonicalize(std::string str) {
  std::string reverse = rcomplement(str);
  int lex_ind = strncmp(str.data(), reverse.data(), str.length());
  if (lex_ind < 0) {
    return str;
  } else {
    return reverse;
  }
}

struct pkmer_t {
  unsigned char data[PACKED_KMER_LEN];

  pkmer_t(const std::string &kmer) {
    packKmer(kmer.data(), data);
  }

  pkmer_t() = default;
  pkmer_t(const pkmer_t&) = default;
  pkmer_t& operator=(const pkmer_t&) = default;

  std::string get() const noexcept {
    char kmer[KMER_LEN];
    unpackKmer(data, kmer);
    return std::string(kmer, KMER_LEN);
  }

  bool operator==(const pkmer_t &pkmer) const noexcept {
    return memcmp(pkmer.data, data, PACKED_KMER_LEN) == 0;
  }

  bool operator!=(const pkmer_t &pkmer) const noexcept {
    return !(*this == pkmer);
  }

  void init(const unsigned char data[PACKED_KMER_LEN]) {
    for (int i = 0; i < PACKED_KMER_LEN; i++) {
      this->data[i] = data[i];
    }
  }

  uint64_t hash() const noexcept {
    return MurmurHash3_x64_64(data, PACKED_KMER_LEN);
  }
};

struct kmer_pair {
  pkmer_t kmer;
  char fb_ext[2];
  int used = 0;

  kmer_pair() = default;
  ~kmer_pair() = default;
  kmer_pair(const kmer_pair&) = default;
  kmer_pair& operator=(const kmer_pair&) = default;

  kmer_pair(const std::string &kmer, const std::string &fb_ext) {
    init(kmer, fb_ext);
  }

  uint64_t hash() const noexcept {
    return kmer.hash();
  }

  void init(const std::string &kmer, const std::string &fb_ext) {
    if (kmer.length() != KMER_LEN || fb_ext.length() != 2) {
      fprintf(stderr, "error: tried to initialize a kmer pair with too short a string.\n");
      return;
    }
    this->kmer = pkmer_t(kmer);
    for (int i = 0; i < 2; i++) {
      this->fb_ext[i] = fb_ext[i];
    }
  }

  void init(const kmer_pair &kmer) {
    this->kmer = kmer.kmer;
    for (int i = 0; i < 2; i++) {
      this->fb_ext[i] = kmer.fb_ext[i];
    }
    this->used = kmer.used;
  }

  bool operator==(const kmer_pair &kmer) const noexcept {
    return kmer.kmer == this->kmer && fb_ext[0] == kmer.fb_ext[0] &&
      fb_ext[1] == kmer.fb_ext[1];
  }

  bool operator!=(const kmer_pair &kmer) const noexcept {
    return !(kmer == *this);
  }

  pkmer_t next_kmer() const noexcept {
    return pkmer_t(kmer_str().substr(1, std::string::npos) + forwardExt());
  }

  pkmer_t last_kmer() const noexcept {
    return pkmer_t(backwardExt() + kmer_str().substr(0, kmer_str().length()-1));
  }

  char forwardExt() const noexcept {
    return fb_ext[1];
  }

  char backwardExt() const noexcept {
    return fb_ext[0];
  }

  std::string kmer_str() const noexcept {
    return kmer.get();
  }

  std::string fb_ext_str() const noexcept {
    return std::string(fb_ext, 2);
  }

  void print() const noexcept {
    printf("%s %s\n", kmer_str().c_str(), fb_ext_str().c_str());
  }
};
