/* The Computer Language Benchmarks Game
   http://shootout.alioth.debian.org/
   contributed by Andrew Moon
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// limit output, so we do not benchmark speed of printing
void puts_limited(char* x) {
  static int left = 550;
  int len = strlen(x);
  if (len <= left) {
    puts(x);
    left -= len;
    return;
  }
  if (left > 0) {
    x[left] = '\0';
    puts(x);
    x[left] = 'z';
    left = 0;
  }
}

struct Random {
  enum { IM = 139968, IA = 3877, IC = 29573 };
  Random() : last(42) {}
  float get(float max = 1.0f) {
    last = (last * IA + IC) % IM;
    return max * last / IM;
  }

protected:
  unsigned int last;
} rng;

struct IUB {
  int c;
  double p;
  unsigned int pi;
};

struct Cumulative {
  enum {
    slots = 512,
  };

  Cumulative(IUB* start) {
    double p = 0;
    for (IUB* iter = start; iter->c; ++iter) {
      p += iter->p;
      iter->p = p < 1.0 ? p : 1.0;
      iter->pi = (unsigned int)(iter->p * slots);
    }

    for (unsigned int i = 0; i <= slots; i++) {
      while (i > start->pi && start->pi != 0) {
        ++start;
      }

      table[i] = start;
    }
  }

  const char operator[](float pct) const {
    IUB* iter = table[(unsigned int)(pct * slots)];
    while (iter->p < pct)
      ++iter;
    return iter->c;
  }

protected:
  IUB* table[slots + 1];
};

static const size_t lineLength = 60;

struct LineBuffer {
  LineBuffer() : lastN(0) {}
  LineBuffer& genrand(Cumulative& table, size_t N) {
    // assert(N <= lineLength);
    for (size_t i = 0; i < N; i++)
      buffer[i] = table[rng.get()];
    buffer[N] = '\n';
    buffer[N + 1] = '\0';
    lastN = N + 1;
    return *this;
  }
  void writeline() { puts_limited(buffer); }

protected:
  char buffer[lineLength + 2];
  size_t lastN;
};

struct RotatingString {
  RotatingString(const char* in) : pos(0) {
    size = strlen(in);
    buffer = new char[size + lineLength];
    memcpy(buffer, in, size);
    memcpy(buffer + size, in, lineLength);
  }
  ~RotatingString() { delete[] buffer; }
  void write(size_t bytes) {
    char* temp = new char[bytes + 2];
    memcpy(temp, buffer + pos, bytes);
    temp[bytes] = '\n';
    temp[bytes] = '\0';
    puts_limited(temp);
    delete temp;
    pos += bytes;
    if (pos > size)
      pos -= size;
  }

protected:
  char* buffer;
  size_t size, pos;
};

template<class Output>
void makeFasta(const char* id, const char* desc, size_t N, Output& output) {
  while (N) {
    const size_t bytes = N < lineLength ? N : lineLength;
    output.writeline(bytes);
    N -= bytes;
  }
}

struct Repeater {
  Repeater(const char* alu) : rot(alu) {}
  void writeline(size_t bytes) { rot.write(bytes); }
  void run(const char* id, const char* desc, size_t N) {
    makeFasta(id, desc, N, *this);
  }

protected:
  RotatingString rot;
};

struct Randomized {
  Randomized(IUB* start) : table(start) {}
  void writeline(size_t bytes) { line.genrand(table, bytes).writeline(); }
  void run(const char* id, const char* desc, size_t N) {
    makeFasta(id, desc, N, *this);
  }

protected:
  Cumulative table;
  LineBuffer line;
};

IUB iub[] = {
  {'a', 0.27, 0},
  {'c', 0.12, 0},
  {'g', 0.12, 0},
  {'t', 0.27, 0},

  {'B', 0.02, 0},
  {'D', 0.02, 0},
  {'H', 0.02, 0},
  {'K', 0.02, 0},
  {'M', 0.02, 0},
  {'N', 0.02, 0},
  {'R', 0.02, 0},
  {'S', 0.02, 0},
  {'V', 0.02, 0},
  {'W', 0.02, 0},
  {'Y', 0.02, 0},
  {0, 0, 0},
};

IUB homosapiens[] = {
  {'a', 0.3029549426680, 0},
  {'c', 0.1979883004921, 0},
  {'g', 0.1975473066391, 0},
  {'t', 0.3015094502008, 0},
  {0, 0, 0},
};

static const char alu[] = "GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTG"
                          "GGAGGCCGAGGCGGGCGGATCACCTGAGGTCAGGAGTTCGA"
                          "GACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAA"
                          "AATACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAAT"
                          "CCCAGCTACTCGGGAGGCTGAGGCAGGAGAATCGCTTGAAC"
                          "CCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTG"
                          "CACTCCAGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAA";

int main(int argc, const char* argv[]) {
  const size_t n = (argc > 1) ? atoi(argv[1]) : 512;

  Repeater(alu).run("ONE", "Homo sapiens alu", n * 2);
  Randomized(iub).run("TWO", "IUB ambiguity codes", n * 3);
  Randomized(homosapiens).run("THREE", "Homo sapiens frequency", n * 5);

  return 0;
}
