#include "element.h"

// Alokuje prazdny element.
element * element_alloc (void)
{
  element * el = malloc (sizeof (element));
  (* el).data = NULL;
  (* el).type = ELEMENT_TYPE_NULL;
  return el;
}

// Alokuje element ze struktury PC (CELL).
element * element_alloc_cell (cell * pc)
{
  element * el = malloc (sizeof (element));
  (* el).data = (void *) pc;
  (* el).type = ELEMENT_TYPE_CELL;
  return el;
}

// Alokuje element ze struktury LIST (seznam).
element * element_alloc_list (list * pl)
{
  element * el = malloc (sizeof (element));
  (* el).data = (void *) pl;
  (* el).type = ELEMENT_TYPE_LIST;
  return el;
}

// Alokuje element z celeho cisla a vrati na nej ukazatel.
element * element_alloc_long_int (long int * li)
{
  element * el = malloc (sizeof (element));
  (* el).data = li;
  (* el).type = ELEMENT_TYPE_LONG_INT;
  return el;
}

element * element_create_long_int (long int li)
{
  element * el = malloc (sizeof (element));
  (* el).data = malloc(sizeof(long int));
  *((long int *) el->data) = li;
  (* el).type = ELEMENT_TYPE_LONG_INT;
  return el;
}

// Alokuje element z realneho cisla a vrati na nej ukazatel.
element * element_alloc_double (double * d)
{
  element * el = malloc (sizeof (element));
  (* el).data = d;
  (* el).type = ELEMENT_TYPE_DOUBLE;
  return el;
}

// Alokuje element z retezce STRING a vrati na nej ukazatel.
element * element_alloc_string (char * string)
{
  element * el = malloc (sizeof (element));
  (* el).data = string;
  (* el).type = ELEMENT_TYPE_STRING;
  return el;
}

// Alokuje element z retezce STRING a vrati na nej ukazatel.
element * element_alloc_const_string (const char * string)
{
  unsigned int i, l;
  char *pch;
  l = strlen(string); // Delka retezce
  pch = malloc(sizeof(char)*(l+1)); // Alokace retezce

  // Naplneni alokovaneho retezce datyz retezce STRING.
  for (i = 0; i<l; i++) {
    pch[i] = string[i];
  }
  pch[l] = NULL;

  element * el = malloc (sizeof (element));
  (* el).data = pch;
  (* el).type = ELEMENT_TYPE_STRING;
  return el;
}

// Alokuje element z matice MAT a vrati na nej ukazatel.
element * element_alloc_matrix (matrix * mat)
{
  element * el = malloc (sizeof (element));
  (* el).data = (void *) mat;
  (* el).type = ELEMENT_TYPE_MATRIX;
  return el;
}

// Uvloni z pameti element EL. Tato funkce neuvolni z pameti ELEMENT.DATA.
void element_free (element *el)
{
  
  if (ELEMENT_TYPE_NULL == (*el).type) {
    free (el);
    return;
  }
  if (ELEMENT_TYPE_LONG_INT == (*el).type) {
    long int * pli = (long int*) (*el).data;
    free (pli);
  }
  if (ELEMENT_TYPE_STRING == (*el).type) {
    char * pch = (char*) (*el).data;
    free (pch);
  }
  if (ELEMENT_TYPE_DOUBLE == (*el).type) {
    double * pd =(double*) (*el).data;
    free (pd);
  }
  if (ELEMENT_TYPE_MATRIX == (*el).type) {
    matrix_free ((matrix*) (*el).data);
  }
  if (ELEMENT_TYPE_CELL == (*el).type) {
    cell_free ((cell*) (*el).data);
  }
  if (ELEMENT_TYPE_LIST == (*el).type) {
    list_free ((list*) (*el).data);
  }
  free (el);
}

element * element_clone (element * el)
{
  unsigned long int i, n;
  if (ELEMENT_TYPE_NULL == (* el).type) {
    return element_alloc();
  }
  if (ELEMENT_TYPE_LONG_INT == (* el).type) {
    long int *pli = malloc(sizeof(long int));
    *pli = *((long int *) (* el).data);
    return element_alloc_long_int(pli);
  }
  if (ELEMENT_TYPE_STRING == (* el).type) {
    char *ch = (char *) (*el).data;
    n = strlen(ch)+1;
    char *pch = malloc(sizeof(char)*n);
    for(i = 0; i<n; i++) {
      pch[i] = ch[i];
    }
    return element_alloc_string(pch);
  }
  if (ELEMENT_TYPE_DOUBLE == (* el).type) {
    double *pd = malloc(sizeof(double));
    *pd = *((double *) (* el).data);
    return element_alloc_double(pd);
  }
  if (ELEMENT_TYPE_MATRIX == (* el).type) {
    return element_alloc_matrix(matrix_clone((matrix*)(*el).data));
  }
  if (ELEMENT_TYPE_CELL == (* el).type) {
    return element_alloc_cell(cell_clone((cell*)(*el).data));
  }
  printf("%s:%i: Element ma neznamy typ \"%i\".\n", __FILE__, __LINE__, (* el).type);
  return NULL;
}


element * element_duplicate (element * el)
{
  if (ELEMENT_TYPE_NULL == (* el).type) {
    return element_alloc();
  }
  if (ELEMENT_TYPE_LONG_INT == (* el).type) {
    long int *pli;
    pli = ((long int *) (* el).data);
    return element_alloc_long_int(pli);
  }
  if (ELEMENT_TYPE_STRING == (* el).type) {
    char *ch = (char *) (*el).data;
    return element_alloc_string(ch);
  }
  if (ELEMENT_TYPE_DOUBLE == (* el).type) {
    double *pd;
    pd = ((double *) (* el).data);
    return element_alloc_double(pd);
  }
  if (ELEMENT_TYPE_MATRIX == (* el).type) {
    return element_alloc_matrix((matrix*)(*el).data);
  }
  if (ELEMENT_TYPE_CELL == (* el).type) {
    return element_alloc_cell((cell*)(*el).data);
  }
  return NULL;
}

void element_throw_away (element * el)
{
  if (ELEMENT_TYPE_CELL == (* el).type) {
    cell_throw_away ((cell*) (*el).data);
  }
  if (ELEMENT_TYPE_LIST == (* el).type) {
    list_throw_away ((cell*) (*el).data);
  }
  free(el);
}

char * element_get_string (element * el)
{
  #ifdef ELEMENT_DEBUGING
  if ((* el).type == ELEMENT_TYPE_STRING) {
  #endif
    return (char *) (* el).data;
  #ifdef ELEMENT_DEBUGING
  } else { 
    printf ("%s:%i: Prvek neni typu (char *).\n", __FILE__, __LINE__);
    exit(1);
  }
  #endif
  return NULL;
}

long int * element_get_long_int (element * el)
{
  #ifdef ELEMENT_DEBUGING
  if ((* el).type == ELEMENT_TYPE_LONG_INT) {
  #endif
    return (long int *) (* el).data;
  #ifdef ELEMENT_DEBUGING
  } else {  
    printf ("%s:%i: Prvek neni typu long int.\n", __FILE__, __LINE__);
    exit(1);
  }
  #endif
  return NULL;
}

double *  element_get_double (element * el)
{
  #ifdef ELEMENT_DEBUGING
  if ((* el).type == ELEMENT_TYPE_DOUBLE) {
  #endif
    return (double *) (* el).data;
  #ifdef ELEMENT_DEBUGING
  } else { 
    printf ("%s:%i: Prvek neni typu double.\n", __FILE__, __LINE__);
    exit(1);
  }
  #endif
  return NULL;
}

matrix * element_get_matrix (element * el)
{
  #ifdef ELEMENT_DEBUGING
  if ((* el).type == ELEMENT_TYPE_MATRIX) {
  #endif
    return (matrix *) (* el).data;
  #ifdef ELEMENT_DEBUGING
  } else {
    fprintf (stderr ,"%s:%i: Prvek neni matice.\n", __FILE__, __LINE__);
    exit(1);
  }
  #endif
  return NULL;
}


cell * element_get_cell (element * el)
{
  #ifdef ELEMENT_DEBUGING
  if ((* el).type == ELEMENT_TYPE_CELL) {
  #endif
    return (cell *) (* el).data;
  #ifdef ELEMENT_DEBUGING
  } else {
    printf ("%s:%i: Prvek neni typu (cell *).\n", (const char *) __FILE__, (int) __LINE__);
    exit(1);
  }
  #endif
  return NULL;
}

list * element_get_list (element * el)
{
  #ifdef ELEMENT_DEBUGING
  if ((* el).type == ELEMENT_TYPE_LIST) {
  #endif
    return (list *) (* el).data;
  #ifdef ELEMENT_DEBUGING
  } else {
    printf ("%s:%i: Prvek neni typu (list *).\n", (const char *) __FILE__, (int) __LINE__);
    exit(1);
  }
  #endif
  return NULL;
}

// Vrati 1 pokud je element A stejny jako element B.
int element_equal (element * A, element * B)
{
  if ((* A).type == (* B).type) {
    if ((* A).type == ELEMENT_TYPE_LONG_INT) {
      long int * lia, * lib;
      lia = (long int *) (* A).type;
      lib = (long int *) (* A).type;
      return * lia == * lib;
    }
    if ((* A).type == ELEMENT_TYPE_DOUBLE) {
      double * da, * db;
      da = (double *) (* A).type;
      db = (double *) (* A).type;
      return * da == * db;
    }
    if ((* A).type == ELEMENT_TYPE_STRING) {
      char * pcha = element_get_string (A);
      char * pchb = element_get_string (B);
      return strcmp (pcha, pchb);
   }
    if ((* A).type == ELEMENT_TYPE_MATRIX) {
      matrix * pmata = element_get_matrix (A);
      matrix * pmatb = element_get_matrix (B);
      return matrix_equal (pmata, pmatb);
    }
  }
  return 0;
}

// Vytiskne prvek EL.
void element_print (element * el)
{
  element_print_indent (el, 0);
}

// Vytiskne prvek EL s odsazenim INDENT.
void element_print_indent (element * el, unsigned int indent)
{
  if ((* el).type == ELEMENT_TYPE_CELL) {
    //printf("%s:%i: Funkce ktera ma tisknout obsah struktury cell neni dodelana. Z toho duvodu se tiskne alespon jeji obsah.", __FILE__, __LINE__);
    cell_print_indent ((cell *) (* el).data, indent);
    return;
  }
  if ((* el).type == ELEMENT_TYPE_LIST) {
    list_print_indent ((list *) (* el).data, indent);
    return;
  }
  // Odsazeni.
  int i;
  for (i = 0; i < indent; i++) {
    printf ("  ");
  }
  if ((* el).type == ELEMENT_TYPE_NULL) {
    printf ("NULL\n");
    return;
  }
  if ((* el).type == ELEMENT_TYPE_DOUBLE) {
    printf ("%f\n", * ((double *) (* el).data));
    return;
  }
  if ((* el).type == ELEMENT_TYPE_LONG_INT) {
    printf ("%li\n", * ((long int *) (* el).data));
    return;
  }
  if ((* el).type == ELEMENT_TYPE_MATRIX) {
    matrix_print ((matrix *) (* el).data);
    return;
  }
  if ((* el).type == ELEMENT_TYPE_STRING) {
    printf ("%s\n", (const char *) (* el).data);
    return;
  }
  
  
}


// Vytiskne informace o obsahu elementu EL.
void element_print_info (element * el)
{
  element_print_info_indent (el, 0);
}


// Vytiskne informace o obsahu elementu EL. Paramet INDENT znamena odsazeni od
// leveho okraje.
void element_print_info_indent (element * el, unsigned int indent)
{
  
  int i;
  if ((* el).type == ELEMENT_TYPE_CELL) {
    cell_print_info_indent ((cell *) (* el).data, indent);
    return;
  }
  if ((* el).type == ELEMENT_TYPE_LIST) {
    list_print_info_indent((list *) (* el).data, indent);
    return;
  }
  // Odsazeni.
  for (i = 0; i < indent; i++) {
    printf ("  ");
  }
  if ((* el).type == ELEMENT_TYPE_NULL) {
    printf ("null element\n");
    return;
  }
  if ((* el).type == ELEMENT_TYPE_DOUBLE) {
    printf ("double\n");
    return;
  }
  if ((* el).type == ELEMENT_TYPE_LONG_INT) {
    printf ("long int\n");
    return;
  }
  if ((* el).type == ELEMENT_TYPE_MATRIX) {
    printf ("matrix (%i x %i)\n", matrix_size_m ((matrix *) (* el).data), matrix_size_n ((matrix *) (* el).data));
    return;
  }
  if ((* el).type == ELEMENT_TYPE_STRING) {
    printf ("string (%i)\n", (int)strlen((char*)(*el).data));
    return;
  }
}
