/* objcode.c: Quantum object code functions

   Copyright 2003 Bjoern Butscher, Hendrik Weimer

   This file is part of libquantum

   libquantum is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published
   by the Free Software Foundation; either version 3 of the License,
   or (at your option) any later version.

   libquantum is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with libquantum; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
   MA 02110-1301, USA

*/

#include <stdarg.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>

#include "objcode.h"
#include "config.h"
#include "matrix.h"
#include "qureg.h"
#include "gates.h"
#include "measure.h"
#include "error.h"

/* status of the objcode functionality (0 = disabled) */

int opstatus = 0;

/* Generated OBJCODE data */

unsigned char *objcode = 0;

/* Current POSITION of the last instruction in the OBJCODE array */

unsigned long position = 0;

/* Number of ALLOCATED pages */

unsigned long allocated = 0;

/* file to write the object code to, if not given */

char *globalfile;

/* Convert a big integer to a byte array */

void
quantum_mu2char(MAX_UNSIGNED mu, unsigned char *buf)
{
  int i, size;
  
  size = sizeof(MAX_UNSIGNED);

  for(i=0; i<size; i++)
    {
      buf[i] = mu / ((MAX_UNSIGNED) 1 << ((size - i - 1) * 8));
      mu %= (MAX_UNSIGNED) 1 << ((size - i - 1) * 8);
    }
}

/* Convert an integer to a byte array */

void
quantum_int2char(int j, unsigned char *buf)
{
  int i, size;
  
  size = sizeof(int);

  for(i=0; i<size; i++)
    {
      buf[i] = j / (1 << ((size - i - 1) * 8));
      j %= (1 << ((size - i - 1) * 8));
    }
}

/* Copy the binary representation of a double to a byte array */

void
quantum_double2char(double d, unsigned char *buf)
{
  int i;
  unsigned char *p = (unsigned char *) &d;

  for(i=0; i<sizeof(double); i++)
    buf[i] = p[i];
}

MAX_UNSIGNED quantum_char2mu(unsigned char *buf)
{
  int i, size;
  MAX_UNSIGNED mu = 0;

  size = sizeof(MAX_UNSIGNED);

  for(i=size-1; i>=0 ; i--)
    mu += buf[i] * ((MAX_UNSIGNED) 1 << (8 * (size - i - 1)));

  return mu;
}

int quantum_char2int(unsigned char *buf)
{
  int i, size;
  int j = 0;

  size = sizeof(int);

  for(i=size-1; i>=0 ; i--)
    j += buf[i] * (1 << (8 * (size - i - 1)));

  return j;
}

double quantum_char2double(unsigned char *buf)
{
  double *d = (double *) buf;

  return *d;
}


/* Start object code recording */

void
quantum_objcode_start()
{
  opstatus = 1;
  allocated = 1;
  objcode = malloc(OBJCODE_PAGE * sizeof(char));

  if(!objcode)
    quantum_error(QUANTUM_ENOMEM);

  quantum_memman(OBJCODE_PAGE * sizeof(char));
}

/* Stop object code recording */

void
quantum_objcode_stop()
{
  opstatus = 0;
  free(objcode);
  objcode = 0;
  quantum_memman(- allocated * OBJCODE_PAGE * sizeof(char));
  allocated = 0;
}

/* Store an operation with its arguments in the object code data */

int
quantum_objcode_put(unsigned char operation, ...)
{
  int i, size = 0;
  va_list args;
  unsigned char buf[80];
  double d;
  MAX_UNSIGNED mu;

  if(!opstatus)
    return 0;

  va_start(args, operation);
  
  buf[0] = operation;
  
  switch(operation)
    {
    case INIT:
      mu = va_arg(args, MAX_UNSIGNED);
      quantum_mu2char(mu, &buf[1]);
      size = sizeof(MAX_UNSIGNED) + 1;
      break;
    case CNOT:
    case COND_PHASE:
      i = va_arg(args, int);
      quantum_int2char(i, &buf[1]);
      i = va_arg(args, int);
      quantum_int2char(i, &buf[sizeof(int)+1]);
      size = 2 * sizeof(int) + 1;
      break;
    case TOFFOLI:
      i = va_arg(args, int);
      quantum_int2char(i, &buf[1]);
      i = va_arg(args, int);
      quantum_int2char(i, &buf[sizeof(int)+1]);
      i = va_arg(args, int);
      quantum_int2char(i, &buf[2*sizeof(int)+1]);
      size = 3 * sizeof(int) + 1;
      break;
    case SIGMA_X:
    case SIGMA_Y:
    case SIGMA_Z:
    case HADAMARD:
    case BMEASURE:
    case BMEASURE_P:
    case SWAPLEADS:
      i = va_arg(args, int);
      quantum_int2char(i, &buf[1]);
      size = sizeof(int) + 1;
      break;
    case ROT_X:
    case ROT_Y:
    case ROT_Z:
    case PHASE_KICK:
    case PHASE_SCALE:
      i = va_arg(args, int);
      d = va_arg(args, double);
      quantum_int2char(i, &buf[1]);
      quantum_double2char(d, &buf[sizeof(int)+1]);
      size = sizeof(int) + sizeof(double) + 1;
      break;
    case CPHASE_KICK:
      i = va_arg(args, int);
      quantum_int2char(i, &buf[1]);
      i = va_arg(args, int);
      quantum_int2char(i, &buf[sizeof(int)+1]);
      d = va_arg(args, double);
      quantum_double2char(d, &buf[2*sizeof(int)+1]);
      size = 2 * sizeof(int) + sizeof(double) + 1;
      break;
    case MEASURE:
    case NOP:
      size = 1;
      break;
    default:
      quantum_error(QUANTUM_EOPCODE);
    }
  
  if((position+size) / OBJCODE_PAGE > position / OBJCODE_PAGE)
    {
      allocated++;
      objcode = realloc(objcode, allocated * OBJCODE_PAGE);

      if(!objcode)
	quantum_error(QUANTUM_ENOMEM);

      quantum_memman(OBJCODE_PAGE * sizeof(char));
    }

  for(i=0; i<size; i++)
    {
      objcode[position] = buf[i];
      position++;
    }

  return 1;
}

/* Save the recorded object code data to a file */

int
quantum_objcode_write(char *file)
{
  FILE *fhd;

  if(!opstatus)
    {
      fprintf(stderr, "Object code generation not active! Forgot to call quantum_objcode_start?\n");
      return 1;
    }

  if(!file)
    file = globalfile;
  
  fhd = fopen(file, "w");

  if (fhd == 0)
    return -1;

  fwrite(objcode, position, 1, fhd);

  fclose(fhd);

  return 0;
}

/* Set a global variable containing the file to write the data to */

void
quantum_objcode_file(char *file)
{
  globalfile = file;
}

/* This function is used as a hook before exiting, as atexit(3) does
   not support to supply arguments to a function */

void
quantum_objcode_exit(char *file)
{
  quantum_objcode_write(0);
  quantum_objcode_stop();
}

/* Execute the contents of an object code file */

void
quantum_objcode_run(char *file, quantum_reg *reg)
{
  int i, j, k, l;
  FILE *fhd;
  unsigned char operation;
  unsigned char buf[OBJBUF_SIZE];
  MAX_UNSIGNED mu;
  double d;

  fhd = fopen(file, "r");

  if(!fhd)
    {
      fprintf(stderr, "quantum_objcode_run: Could not open %s: ", file);
      perror(0);
      return;
    }

  for(i=0; !feof(fhd); i++)
    {
      for(j=0; j<OBJBUF_SIZE; j++)
	buf[j] = 0;
      
      operation = fgetc(fhd);
      switch(operation)
	{
	case INIT:
	  if(!fread(buf, sizeof(MAX_UNSIGNED), 1, fhd))
	    {
	      quantum_error(QUANTUM_FAILURE);
	      break;
	    }
	  mu = quantum_char2mu(buf);
	  *reg = quantum_new_qureg(mu, 12);

	  break;

	case CNOT:
	case COND_PHASE:
	  if(!fread(buf, sizeof(int), 1, fhd))
	    {
	      quantum_error(QUANTUM_FAILURE);
	      break;
	    }
	  j = quantum_char2int(buf);
	  if(!fread(buf, sizeof(int), 1, fhd))
	    {
	      quantum_error(QUANTUM_FAILURE);
	      break;
	    }
	  
	  k = quantum_char2int(buf);
	  switch(operation)
	    {
	    case CNOT: quantum_cnot(j, k, reg);
	      break;
	    case COND_PHASE: quantum_cond_phase(j, k, reg);
	      break;
	    }
	  break;

	case TOFFOLI:
	  if(!fread(buf, sizeof(int), 1, fhd))
	    {
	      quantum_error(QUANTUM_FAILURE);
	      break;
	    }
	  j = quantum_char2int(buf);
	  if(!fread(buf, sizeof(int), 1, fhd))
	    {
	      quantum_error(QUANTUM_FAILURE);
	      break;
	    }
	  k = quantum_char2int(buf);
	  if(!fread(buf, sizeof(int), 1, fhd))
	    {
	      quantum_error(QUANTUM_FAILURE);
	      break;
	    }
	  l = quantum_char2int(buf);
	  quantum_toffoli(j, k, l, reg);
	  break;

	case SIGMA_X:
	case SIGMA_Y:
	case SIGMA_Z:
	case HADAMARD:
	case BMEASURE:
	case BMEASURE_P:
	case SWAPLEADS:
	  if(!fread(buf, sizeof(int), 1, fhd))
	    {
	      quantum_error(QUANTUM_FAILURE);
	      break;
	    }
	  j = quantum_char2int(buf);
	  switch(operation)
	    {
	    case SIGMA_X: quantum_sigma_x(j, reg);
	      break;
	    case SIGMA_Y: quantum_sigma_y(j, reg);
	      break;
	    case SIGMA_Z: quantum_sigma_z(j, reg);
	      break;
	    case HADAMARD: quantum_hadamard(j, reg);
	      break;
	    case BMEASURE: quantum_bmeasure(j, reg);
	      break;
	    case BMEASURE_P: quantum_bmeasure_bitpreserve(j, reg);
	      break;
	    case SWAPLEADS: quantum_swaptheleads(j, reg);
	      break;
	    }
	  break;

	case ROT_X:
	case ROT_Y:
	case ROT_Z:
	case PHASE_KICK:
	case PHASE_SCALE:
	  if(!fread(buf, sizeof(int), 1, fhd))
	    {
	      quantum_error(QUANTUM_FAILURE);
	      break;
	    }
	  j = quantum_char2int(buf);
	  if(!fread(buf, sizeof(double), 1, fhd))
	    {
	      quantum_error(QUANTUM_FAILURE);
	      break;
	    }
	  d = quantum_char2double(buf);
	  switch(operation)
	    {
	    case ROT_X: quantum_r_x(j, d, reg);
	      break;
	    case ROT_Y: quantum_r_y(j, d, reg);
	      break;
	    case ROT_Z: quantum_r_z(j, d, reg);
	      break;
	    case PHASE_KICK: quantum_phase_kick(j, d, reg);
	      break;
	    case PHASE_SCALE: quantum_phase_scale(j, d, reg);
	      break;
	    }
	  break;

	case CPHASE_KICK:
	  if(!fread(buf, sizeof(int), 1, fhd))	
	    {
	      quantum_error(QUANTUM_FAILURE);
	      break;
	    }
	  j = quantum_char2int(buf);
	  if(!fread(buf, sizeof(int), 1, fhd))
	    {
	      quantum_error(QUANTUM_FAILURE);
	      break;
	    }
	  k = quantum_char2int(buf);
	  if(!fread(buf, sizeof(double), 1, fhd))
	    {
	      quantum_error(QUANTUM_FAILURE);
	      break;
	    }
	  d = quantum_char2double(buf);
	  quantum_cond_phase_kick(j, k, d, reg);
	  break;
	  
	case MEASURE: quantum_measure(*reg);
	  break;

	case NOP:
	  break;

	default:
	  fprintf(stderr, "%i: Unknown opcode 0x(%X)!\n", i, operation);
	  return;
	}

    }

  fclose(fhd);

}  
