#include <stdio.h>
#include "switching_matrix.h"
#include "encrypted_domain.h"
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include <stdio.h>

#include "./clinet_server.c"
#include "./encrypted_domain.c"
#include "./encrypted_domain.h"
#include "./mat.h"

/* reset the accelerator */
void encrypted_domain_reset(const encrypted_domain_status_t *c)
{
  encrypted_domain_arg_t eda;
  eda.status_info = *c;
  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_RESET_WRITE, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_RESET_WRITE) failed");
      return;
  }
}

void encrypted_domain_all_loaded(const encrypted_domain_status_t *c)
{
  encrypted_domain_arg_t eda;
  eda.status_info = *c;
  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_RESET_WRITE, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_LOADED_WRITE) failed");
      return;
  }

static void encrypted_domain_write_op_type(encrypted_domain_loading_t load_info)
{
  encrypted_domain_arg_t eda;
  eda.load_info = *c;
  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_OP_TYPE_WRITE, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_OP_TYPE_WRITE) failed");
      return;
  }
}

static void encrypted_domain_write_data_type(encrypted_domain_loading_t load_info)
{
  encrypted_domain_arg_t eda;
  eda.load_info = *c;
  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_DATA_TYPE_WRITE, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_DATA_TYPE_WRITE) failed");
      return;
  }
}

static void encrypted_domain_write_width(encrypted_domain_loading_t load_info)
{
  encrypted_domain_arg_t eda;
  eda.load_info = *c;
  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_WIDTH_WRITE, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_WIDTH_WRITE) failed");
      return;
  }
}

static void encrypted_domain_write_length(encrypted_domain_loading_t load_info)
{
  encrypted_domain_arg_t eda;
  eda.load_info = *c;
  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_LENGTH_WRITE, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_LENGTH_WRITE) failed");
      return;
  }
}

static void encrypted_domain_write_ell(encrypted_domain_loading_t load_info)
{
  encrypted_domain_arg_t eda;
  eda.load_info = *c;
  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_ELL_WRITE, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_ELL_WRITE) failed");
      return;
  }
}

/* write to the accelerator */
void encrypted_domain_write_data(const encrypted_domain_loading_t *c)
{
  encrypted_domain_arg_t eda;
  eda.load_info = *c;
  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_WRITE, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_WRITE) failed");
      return;
  }
}

/*---------------- read from the accelerator ----------------*/
int encrypted_domain_read_data_0(const encrypted_domain_loading_t *c)
{
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_0, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_0) failed");
      return;
  } else eda.output_info.output;
}

int encrypted_domain_read_data_1(const encrypted_domain_loading_t *c)
{
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_1, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_1) failed");
      return;
  } else eda.output_info.output;
}

int encrypted_domain_read_data_2(const encrypted_domain_loading_t *c)
{
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_2, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_2) failed");
      return;
  } else eda.output_info.output;
}

int encrypted_domain_read_data_3(const encrypted_domain_loading_t *c)
{
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_3, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_3) failed");
      return;
  } else eda.output_info.output;
}

int encrypted_domain_read_data_4(const encrypted_domain_loading_t *c)
{
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_4, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_4) failed");
      return;
  } else eda.output_info.output;
}

int encrypted_domain_read_data_5(const encrypted_domain_loading_t *c)
{
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_5, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_0) failed");
      return;
  } else eda.output_info.output;
}

int encrypted_domain_read_data_6(const encrypted_domain_loading_t *c)
{
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_6, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_6) failed");
      return;
  } else eda.output_info.output;
}

int encrypted_domain_read_data_7(const encrypted_domain_loading_t *c)
{
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_7, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_7) failed");
      return;
  } else eda.output_info.output;
}

int encrypted_domain_read_data_8(const encrypted_domain_loading_t *c)
{
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_8, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_8) failed");
      return;
  } else eda.output_info.output;
}

int encrypted_domain_read_data_9(const encrypted_domain_loading_t *c)
{
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_9, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_9) failed");
      return;
  } else eda.output_info.output;
}

int encrypted_domain_read_data_10(const encrypted_domain_loading_t *c)
{
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_10, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_10) failed");
      return;
  } else eda.output_info.output;
}

int encrypted_domain_read_data_11(const encrypted_domain_loading_t *c)
{
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_11, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_11) failed");
      return;
  } else eda.output_info.output;
}

int encrypted_domain_read_data_12(const encrypted_domain_loading_t *c)
{
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_12, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_12) failed");
      return;
  } else eda.output_info.output;
}

int encrypted_domain_read_data_13(const encrypted_domain_loading_t *c)
{
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_13, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_13) failed");
      return;
  } else eda.output_info.output;
}

int encrypted_domain_read_data_14(const encrypted_domain_loading_t *c)
{
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_14, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_14) failed");
      return;
  } else eda.output_info.output;
}

int encrypted_domain_read_data_15(const encrypted_domain_loading_t *c)
{
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_15, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_15) failed");
      return;
  } else eda.output_info.output;
}

/*---------------- read from the accelerator ----------------*/


static void encrypted_domain_read_length(encrypted_domain_output_t output_info)
{	
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_LENGTH, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_LENGTH) failed");
      return;
  } else eda.output_info.output;
}

static void encrypted_domain_read_width(encrypted_domain_output_t output_info)
{	
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_WDITH, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_WDITH) failed");
      return;
  } else eda.output_info.output;
}

static void encrypted_domain_read_done(encrypted_domain_output_t output_info)
{	
  encrypted_domain_arg_t eda;

  if (ioctl(encrypted_domain_fd, ENCRYPTED_DOMAIN_READ_DONE, &eda)) {
      perror("ioctl(ENCRYPTED_DOMAIN_READ_DONE) failed");
      return;
  } else eda.output_info.output;
}

// accelerator part
Mat *serverAddition(Mat *a, Mat *b, encrypted_domain_arg_t *eba);
Mat *serverLinearTransform(Mat *mat, Mat *c, encrypted_domain_arg_t *eba);
Mat *serverInnerProduct(Mat *c1, Mat *c2, int w, Mat *mat, encrypted_domain_arg_t *eba);

Mat *serverAddition(Mat *a, Mat *b, encrypted_domain_arg_t *eba){
    // initialize empty vector
    Mat *s = newmat(a->row, 1, 0);

    eba.status_info.reset = 1;
    encrypted_domain_reset(&eba.status_info);
    eba.status_info.reset = 0;
    encrypted_domain_reset(&eba.status_info);
    
    // load operation type
    // tell hardware to evoke vector_addition module
    eba.load_info.op_type = 0;
    encrypted_domain_write_op_type(&eba.load_info);

    // load c_1
    eba.load_info.data_type = 0;
    encrypted_domain_data_type(&eba.load_info);
    for (int i = 0; i < a->row; i++) {
        ksa.load_info.input = get(a, i, 1);
        encrypted_domain_write_data(&eba.load_info);
    }

    // load c_2
    eba.load_info.data_type = 1;
    encrypted_domain_data_type(&eba.load_info);
    for (int i = 0; i < a->row; i++) {
        ksa.load_info.input = get(b, i, 1);
        encrypted_domain_write_data(&eba.load_info);
    }

    // ready
    eba.status_info.all_loaded = 1;
    encrypted_domain_all_loaded(&eba.status_info);

    // read
    set(s, 1, 1, encrypted_domain_read_data_0(&eba.output_info));
    set(s, 2, 1, encrypted_domain_read_data_1(&eba.output_info));
    set(s, 3, 1, encrypted_domain_read_data_2(&eba.output_info));
    set(s, 4, 1, encrypted_domain_read_data_3(&eba.output_info));
    set(s, 5, 1, encrypted_domain_read_data_4(&eba.output_info));
    set(s, 6, 1, encrypted_domain_read_data_5(&eba.output_info));
    set(s, 7, 1, encrypted_domain_read_data_6(&eba.output_info));
    set(s, 8, 1, encrypted_domain_read_data_7(&eba.output_info));
    set(s, 9, 1, encrypted_domain_read_data_8(&eba.output_info));
    set(s, 10, 1, encrypted_domain_read_data_9(&eba.output_info));
    set(s, 11, 1, encrypted_domain_read_data_10(&eba.output_info));
    set(s, 12, 1, encrypted_domain_read_data_11(&eba.output_info));
    set(s, 13, 1, encrypted_domain_read_data_12(&eba.output_info));
    set(s, 14, 1, encrypted_domain_read_data_13(&eba.output_info));
    set(s, 15, 1, encrypted_domain_read_data_14(&eba.output_info));
    set(s, 16, 1, encrypted_domain_read_data_15(&eba.output_info));

    return s;
}

Mat *serverLinearTransform(Mat *mat, Mat *c, int w, encrypted_domain_arg_t *eba){

    // initialize empty vector
    Mat *s = newmat(mat->row, 1, 0);

    eba.status_info.reset = 1;
    encrypted_domain_reset(&eba.status_info);
    eba.status_info.reset = 0;
    encrypted_domain_reset(&eba.status_info);
    
    // load operation type
    // tell hardware to evoke linear_transform module
    eba.load_info.op_type = 1;
    encrypted_domain_write_op_type(&eba.load_info);

    // load c
    eba.load_info.data_type = 3;
    encrypted_domain_data_type(&eba.load_info);
    for (int i = 0; i < a->col; i++) {
        ksa.load_info.input = get(c, i, 1);
        encrypted_domain_write_data(&eba.load_info);
    }

    // load M
    eba.load_info.data_type = 2;
    encrypted_domain_data_type(&eba.load_info);
    for (int i = 0; i < mat->row; i++) {
        for (int j = 0; j < mat->col; j++) {
            eba.load_info.input = get(mat, i, j);
            encrypted_domain_write_data(&eba.load_info);
        }
        eba.status_info.all_loaded = 1;
        encrypted_domain_all_loaded(&eba.status_info);
        // let it compute
        sleep(0.1);

        // read result
        set(s, i, 1, encrypted_domain_read_data_0(&eba.output_info))

        // ready to load a new set
        eba.status_info.all_loaded = 0;
        encrypted_domain_all_loaded(&eba.status_info);
    }

    return s;
}

Mat *serverInnerProduct(Mat *c1, Mat *c2, Mat *mat, encrypted_domain_arg_t *eba) {
    // initialize empty vector
    Mat *s = newmat(c_1->row * c_1->row, 1, 0);

    eba.status_info.reset = 1;
    encrypted_domain_reset(&eba.status_info);
    eba.status_info.reset = 0;
    encrypted_domain_reset(&eba.status_info);
    
    // load operation type
    // tell hardware to evoke weighted_inner_product module
    eba.load_info.op_type = 2;
    encrypted_domain_write_op_type(&eba.load_info);

    // load width

    // load c_1_out
    eba.load_info.data_type = 4;
    encrypted_domain_data_type(&eba.load_info);
    for (int i = 0; i < c_1->col; i++) {
        ksa.load_info.input = get(c, i, 1);
        encrypted_domain_write_data(&eba.load_info);
    }

    // load c_2_out
    eba.load_info.data_type = 5;
    encrypted_domain_data_type(&eba.load_info);
    for (int i = 0; i < c_1->col; i++) {
        ksa.load_info.input = get(c, i, 1);
        encrypted_domain_write_data(&eba.load_info);
    }

    // load w
    eba.load_info.data_type = 6;
    encrypted_domain_data_type(&eba.load_info);
    eba.load_info.w = w;
    encrypted_domain_write_w(&eba.load_info);

    // load M
    eba.load_info.data_type = 2;
    encrypted_domain_data_type(&eba.load_info);
    for (int i = 0; i < mat->row; i++) {
        for (int j = 0; j < mat->col; j++) {
            eba.load_info.input = get(mat, i, j);
            encrypted_domain_write_data(&eba.load_info);
        }
        eba.status_info.all_loaded = 1;
        encrypted_domain_all_loaded(&eba.status_info);
        // let it compute
        sleep(0.1);

        // read result
        set(s, i, 1, encrypted_domain_read_data_0(&eba.output_info))

        // ready to load a new set
        eba.status_info.all_loaded = 0;
        encrypted_domain_all_loaded(&eba.status_info);
    }


    return s;
}
