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

#define MAXCHUNK (2048*1024)

static unsigned int seed;

void my_srand(unsigned int n)
{
  seed = n & 0xffff;
}

unsigned int my_rand()
{
  seed = (seed * 2109 + 9273) & 0x7fff;
  return (seed + 0xc000) & 0xffff;
}

void load(FILE *fh, unsigned char *ptr, unsigned long sz)
{
  if(fread(ptr, 1, sz, fh) != sz)
    {
      fprintf(stderr, "Read error!\n");
      exit(1);
    }
}

void load_chunk(FILE *fh, unsigned char *ptr, unsigned long sz)
{
  static int idx[MAXCHUNK/32];
  int i;

  /* Convert chunk size to number of slices */
  sz /= 32;

  /* Initialize index table with unity,
     so that each slice gets loaded exactly once */
  for(i = 0; i < sz; i++)
    idx[i] = i;

  for(i = sz-1; i >= 0; --i)
    {
      /* Select a replacement index */
      int x = (my_rand() * i) >> 16;

      /* Swap */
      int tmp = idx[i];
      idx[i] = idx[x];
      idx[x] = tmp;

      /* Load resulting slice */
      load(fh, ptr+32*idx[i], 32);
    }
}

void load_file(FILE *fh, unsigned char *ptr, unsigned long filesz)
{
  unsigned long chunksz;

  my_srand(filesz);

  /* Descramble 2 meg blocks for as long as possible, then
     gradually reduce the window down to 32 bytes (1 slice) */
  for(chunksz = MAXCHUNK; chunksz >= 32; chunksz >>= 1)
    while(filesz >= chunksz)
      {
	load_chunk(fh, ptr, chunksz);
	filesz -= chunksz;
	ptr += chunksz;
      }

  /* Load final incomplete slice */
  if(filesz)
    load(fh, ptr, filesz);
}

void read_file(char *filename, unsigned char **ptr, unsigned long *sz)
{
  FILE *fh = fopen(filename, "rb");
  if(fh == NULL)
    {
      fprintf(stderr, "Can't open \"%s\".\n", filename);
      exit(1);
    }
  if(fseek(fh, 0, SEEK_END)<0)
    {
      fprintf(stderr, "Seek error.\n");
      exit(1);
    }
  *sz = ftell(fh);
  *ptr = malloc(*sz);
  if( *ptr == NULL )
    {
      fprintf(stderr, "Out of memory.\n");
      exit(1);
    }
  if(fseek(fh, 0, SEEK_SET)<0)
    {
      fprintf(stderr, "Seek error.\n");
      exit(1);
    }
  load_file(fh, *ptr, *sz);
  fclose(fh);
}

void save(FILE *fh, unsigned char *ptr, unsigned long sz)
{
  if(fwrite(ptr, 1, sz, fh) != sz)
    {
      fprintf(stderr, "Write error!\n");
      exit(1);
    }
}

void save_chunk(FILE *fh, unsigned char *ptr, unsigned long sz)
{
  static int idx[MAXCHUNK/32];
  int i;

  /* Convert chunk size to number of slices */
  sz /= 32;

  /* Initialize index table with unity,
     so that each slice gets saved exactly once */
  for(i = 0; i < sz; i++)
    idx[i] = i;

  for(i = sz-1; i >= 0; --i)
    {
      /* Select a replacement index */
      int x = (my_rand() * i) >> 16;

      /* Swap */
      int tmp = idx[i];
      idx[i] = idx[x];
      idx[x] = tmp;

      /* Save resulting slice */
      save(fh, ptr+32*idx[i], 32);
    }
}

void save_file(FILE *fh, unsigned char *ptr, unsigned long filesz)
{
  unsigned long chunksz;

  my_srand(filesz);

  /* Descramble 2 meg blocks for as long as possible, then
     gradually reduce the window down to 32 bytes (1 slice) */
  for(chunksz = MAXCHUNK; chunksz >= 32; chunksz >>= 1)
    while(filesz >= chunksz)
      {
	save_chunk(fh, ptr, chunksz);
	filesz -= chunksz;
	ptr += chunksz;
      }

  /* Save final incomplete slice */
  if(filesz)
    save(fh, ptr, filesz);
}

void write_file(char *filename, unsigned char *ptr, unsigned long sz)
{
  FILE *fh = fopen(filename, "wb");
  if(fh == NULL)
    {
      fprintf(stderr, "Can't open \"%s\".\n", filename);
      exit(1);
    }
  save_file(fh, ptr, sz);
  fclose(fh);
}

void descramble(char *src, char *dst)
{
  unsigned char *ptr = NULL;
  unsigned long sz = 0;
  FILE *fh;

  read_file(src, &ptr, &sz);

  fh = fopen(dst, "wb");
  if(fh == NULL)
    {
      fprintf(stderr, "Can't open \"%s\".\n", dst);
      exit(1);
    }
  if( fwrite(ptr, 1, sz, fh) != sz )
    {
      fprintf(stderr, "Write error.\n");
      exit(1);
    }
  fclose(fh);
  free(ptr);
}

void scramble(char *src, char *dst)
{
  unsigned char *ptr = NULL;
  unsigned long sz = 0;
  FILE *fh;

  fh = fopen(src, "rb");
  if(fh == NULL)
    {
      fprintf(stderr, "Can't open \"%s\".\n", src);
      exit(1);
    }
  if(fseek(fh, 0, SEEK_END)<0)
    {
      fprintf(stderr, "Seek error.\n");
      exit(1);
    }
  sz = ftell(fh);
  ptr = malloc(sz);
  if( ptr == NULL )
    {
      fprintf(stderr, "Out of memory.\n");
      exit(1);
    }
  if(fseek(fh, 0, SEEK_SET)<0)
    {
      fprintf(stderr, "Seek error.\n");
      exit(1);
    }
  if( fread(ptr, 1, sz, fh) != sz )
    {
      fprintf(stderr, "Read error.\n");
      exit(1);
    }
  fclose(fh);

  write_file(dst, ptr, sz);

  free(ptr);
}

int main(int argc, char *argv[])
{
  int opt = 0;

  if(argc > 1 && !strcmp(argv[1], "-d"))
    opt ++;

  if(argc != 3+opt)
    {
      fprintf(stderr, "Usage: %s [-d] from to\n", argv[0]);
      exit(1);
    }
  
  if(opt)
    descramble(argv[2], argv[3]);
  else
    scramble(argv[1], argv[2]);

  return 0;
}
