// Copyright 2010 The Emscripten Authors.  All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License.  Both these licenses can be
// found in the LICENSE file.

/* 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, 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;
}

