/**
 * Copyright (C) 2011 Anders Sundman <anders@4zm.org>
 *
 * This file is part of mfterm.
 *
 * mfterm 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.
 *
 * mfterm 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 mfterm.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stddef.h>
#include "util.h"
#include "spec_syntax.h"

// Forward declarations
int sp_parse();
extern FILE* sp_in;

// Parse the input file and set up type_root and instance_root.
// Return 0 on success.
int spec_import(FILE* input) {

  // Parse the input to build a type hierarchy
  sp_in = input;
  if (sp_parse()) {
    printf("Syntax Error: Specification not imported\n");
    goto error;
  }

  // Check for missing definitions
  type_t* partial = tt_contains_partial_types();
  if (partial) {
    printf("Error: Incomplete declaration '%s' in specification.\n",
           partial->composite_extras->name);
    goto error;
  }

  // Make sure we have a root type defined
  if (type_root == NULL) {
    printf("Error: No root type '.' found in specification.\n");
    goto error;
  }

  // Create the instance tree
  instance_root = make_instance_tree(type_root);

  printf("Specification sucessfully imported.\n");

  return 0;

 error:
  clear_instance_tree();
  tt_clear();
  return 1;
}

// The primitive Byte type
type_t byte_type = {
  .type_category = BYTE_TYPE,
  .composite_extras = NULL,
};

// The primitive Bit type
type_t bit_type = {
  .type_category = BIT_TYPE,
  .composite_extras = NULL,
};

// Allocate and return a composite type instance. The type will assume
// ownership of the heap allocated name.
type_t* make_composite_type(char* name) {
  type_t* t = malloc(sizeof(type_t));
  t->type_category = COMPOSITE_TYPE;
  t->composite_extras = (composite_type_extras_t*)
    malloc(sizeof(composite_type_extras_t));

  if (name)
    t->composite_extras->name = name;
  else
    t->composite_extras->name = NULL; // Anonymous type

  t->composite_extras->fields = NULL;
  return t;
}

// Free a composite type. This function will also free it's fields.
void free_composite_type(type_t* t) {

  // Free the fields
  field_list_t* iter = t->composite_extras->fields;
  while (iter) {
    field_list_t* tmp = iter;
    iter = iter->next_;
    free_field(tmp->field);
    free(tmp);
  }

  // Free the type data
  free(t->composite_extras->name);
  free(t->composite_extras);
  free(t);
}

// Allocate a new field with the given parameters. Anonymous '-'
// filler fields use NULL as name. The field will assume ownership of
// the heap allocated name.
field_t* make_field(char* name, type_t* type, size_t length) {
  field_t* f = (field_t*) malloc(sizeof(field_t));
  f->name = name; // NULL for fillers
  f->type = type;
  f->length = length;
  return f;
}

// Free the memory used by a field.
void free_field(field_t* field) {
  if (field == NULL)
    return;
  free(field->name);
  free(field);
}

// Add a field to an existing list of fields or, if the field_list
// parameter is NULL, create a new field list. The order of fields is
// significant and this function will append the field to the end of
// the field_list.
field_list_t* append_field(field_list_t* field_list, field_t* field) {

  // Create the list node
  field_list_t* flist = (field_list_t*) malloc(sizeof(field_list_t));
  flist->field = field;
  flist->next_ = NULL;

  // Create the list or append to the end
  if (field_list != NULL) {
    field_list_t* it = field_list;
    while(it->next_)
      it = it->next_;
    it->next_ = flist;
  }
  else {
    field_list = flist; // Won't effect the inarg
  }

  // Return the start of the list
  return field_list;
}

// Search the field list for a field with the given name
field_t* get_field(field_list_t* field_list, const char* name) {
  if (name == NULL || field_list == NULL)
    return NULL;

  field_list_t* it = field_list;
  while(it) {
    field_t* f = it->field;
    if (f && f->name && strcmp(f->name, name) == 0)
      return f;
    it = it->next_;
  }

  // Not found
  return NULL;
}


// The global instance of the type table. If there isn't any, the
// variable will be NULL. All the type table operations (tt_) operate
// on this global variable.
type_table_t* type_table = NULL;

// The root type of the type hierarchy
type_t* type_root = NULL;


// Internal functions for allocating and freeing type table list nodes.
type_table_t* tt_make_node_(type_t* t);
void tt_free_node_(type_table_t* tt);

// Clear the type table - freeing the memory used by the table and by
// all the types.
void tt_clear() {

  // Reset the root
  type_root = NULL;

  // Free all types and the table itself
  type_table_t* it = type_table;
  while(it) {
    type_table_t* next = it->next_;
    free_composite_type(it->type);
    tt_free_node_(it);
    it = next;
  };

  type_table = NULL;
}

// Add a type to the type table.
type_t* tt_add_type(type_t* t) {
  if (type_table == NULL) {
    type_table = tt_make_node_(t);
  }
  else {
    type_table_t* it = type_table;
    while(it->next_)
      it = it->next_;
    it->next_ = tt_make_node_(t);
  }
  return t;
}

// Search the type table for a type with the given name. The first
// type found will be returned. If no type is found, NULL is returned.
type_t* tt_get_type(const char* type_name) {
  if (type_name == NULL)
    return NULL;

  type_table_t* it = type_table;
  while(it) {
    // Anonymous types (name == NULL) will never match
    if (it->type->composite_extras->name &&
        strcmp(type_name, it->type->composite_extras->name) == 0)
      return it->type; // Type was found!
    it = it->next_;
  }

  // Not found
  return NULL;
}

// Check if there are any partially declared types in the type
// table. Return a pointer to the first incomplete type or NULL if
// none exists.
type_t* tt_contains_partial_types() {
  type_table_t* it = type_table;
  while(it) {
    if (it->type->composite_extras->decl_status == PARTIAL_DECL)
      return it->type;
    it = it->next_;
  }
  return NULL;
}

// Allocate a new list entry for the type table
type_table_t* tt_make_node_(type_t* t) {
  type_table_t* tt = malloc(sizeof(type_table_t));
  tt->type = t;
  tt->next_ = NULL;
  return tt;
}

// Free a type table list entry (this won't free the type)
void tt_free_node_(type_table_t* tt) {
  free(tt);
}


// The global variable representing the root instance; it is an
// instanciation of the '.' type.
instance_t* instance_root = NULL;

// Forward decls of internal functions used during creation and
// destruction of the instance tree.
void clear_instance_tree_(instance_t* root);
void make_instance_(instance_t* root,
                    size_t* obytes, size_t* obits,
                    size_t* sbytes, size_t* sbits);
instance_t* make_byte_instance_(field_t* field, size_t* obytes, size_t* obits);
instance_t* make_bit_instance_(field_t* field, size_t* obytes, size_t* obits);
instance_t* make_composite_instance_(field_t* field,
                                     size_t* obytes, size_t* obits);
instance_list_t* append_instance_(instance_list_t** end_ptr,
                                  instance_t* new_field);


// Create an instance tree matching the type tree starting at
// type_root. The global instance tree is constructed with type_root '.'.
instance_t* make_instance_tree(type_t* type_root) {
  instance_t* root = malloc(sizeof(instance_t));
  root->offset_bytes = 0;
  root->offset_bits = 0;
  root->size_bytes = 0;
  root->size_bits = 0;
  root->field = make_field(strdup("."), type_root, 1);
  root->fields = NULL;

  size_t obytes = 0;
  size_t obits = 0;
  make_instance_(root, &obytes, &obits,
                 &(root->size_bytes), &(root->size_bits));
  return root;
}

// Clear the global instance tree. Free it and set instance_root NULL
void clear_instance_tree() {
  if (instance_root == NULL)
    return;
  clear_instance_tree_(instance_root);
  instance_root = NULL;
}

/* Get the child instance with a given name. Only look to children, */
/* not grand children. If no child with the given name exists, return */
/* null. */
instance_t* get_instance_child(instance_t* inst, const char* name) {
  if (name == NULL)
    return NULL;

  return get_instance_child_n(inst, name, strlen(name));
}

/**
 * Like get_instance_child(inst, name), but name does not have to be
 * null terminated. Instead the length of the name string is given by
 * the last argument.
 */
instance_t* get_instance_child_n(instance_t* inst,
                                 const char* name,
                                 size_t nlen) {

  if (inst == NULL || name == NULL || nlen == 0)
    return NULL;

  instance_list_t* iter = inst->fields;
  while(iter) {
    field_t* f = iter->instance->field;

    // Make sure the field has a name (isn't anaonymous) before comparing.
    if (f && f->name &&
        strlen(f->name) == nlen &&
        strncmp(f->name, name, nlen) == 0)
      return iter->instance;
    iter = iter->next_;
  }

  return NULL;
}

/**
 * Parse a specification path of the form '.fu.bar.baz' and return the
 * instance pointed to by baz. In case the path doesn't point to a
 * loaded instance, return NULL. */
instance_t* parse_spec_path(const char* path) {

  const char* lastpath;
  instance_t* inst;

  // Parse the path up to the last instance
  if (parse_partial_spec_path(path, &lastpath, &inst) != 0)
    return NULL;

  // Find and return the leaf node
  return get_instance_child(inst, lastpath);
}

/**
 * Parse the path to produce a parent section and an instance that
 * points to the head of the parent.
 *
 * The format is .fu.bar.ba(z). Where .fu.bar.ba is the path, fu, bar
 * and baz are nested fields. The function should return parent_end
 * pointing into path to the point after the last '.', i.e. to the 'b'
 * in the last 'ba'. parent_inst will point to bar.
 *
 * The function returns 0 on success.
 */
int parse_partial_spec_path(const char* path,
                            const char** parent_end,
                            instance_t** parent_inst) {

  // Check input. Paths start with '.'
  if (path == NULL || path[0] != '.')
    return 1;

  instance_t* inst = instance_root;
  const char* remaining_path = path + 1;

  char* tok_end;
  while((tok_end = strchr(remaining_path, '.')) != NULL) {
    // There is still a part of the path before the last '.'

    ptrdiff_t tok_name = tok_end - remaining_path;
    if (tok_name <= 0)
      return 1;

    inst = get_instance_child_n(inst, remaining_path, (size_t)tok_name);

    // Exit early (error in ancestor path)
    if (inst == NULL)
      return 1;

    // Remove the token and the '.' from the start of remaining string
    remaining_path = tok_end + 1;
  }

  // Set the output arguments
  if (parent_end != NULL)
    *parent_end = remaining_path;
  if (parent_inst != NULL)
    *parent_inst = inst;

  return 0;
}

// Count the number of fields in the instance
int instance_fields_count(instance_t* inst) {
  if (inst == NULL)
    return 0;

  int count = 0;
  instance_list_t* it = inst->fields;
  while (it) {
    ++count;
    it = it->next_;
  }

  return count;
}

void clear_instance_tree_(instance_t* root) {
  instance_list_t* iter = root->fields;
  while (iter) {
    instance_list_t* tmp = iter->next_;
    clear_instance_tree_(iter->instance);
    free(iter);
    iter = tmp;
  }
  free(root);
}

void make_instance_(instance_t* root,
                    size_t* obytes, size_t* obits,
                    size_t* sbytes, size_t* sbits) {

  // Pointers to the matching type field and instance field beign processed.
  field_list_t* tf_iter = root->field->type->composite_extras->fields;
  instance_list_t* if_iter = root->fields;

  // Iterate over all the type fields and add matching instance fields.
  while(tf_iter) {
    field_t* f = tf_iter->field;

    // Create the child sub-tree or instance.
    instance_t* child;
    // Byte
    if (f->type == &byte_type) {
      child = make_byte_instance_(f, obytes, obits);
    }
    // Bit
    else if (f->type == &bit_type) {
      child = make_bit_instance_(f, obytes, obits);
    }
    // Composite type
    else {
      child = make_composite_instance_(f, obytes, obits);
      child->size_bytes = 0;
      child->size_bits = 0;

      make_instance_(child, obytes, obits,
                     &(child->size_bytes), &(child->size_bits));

      // Update size with bits % 8
      child->size_bytes =
        child->size_bytes * f->length +
        (child->size_bits * f->length) / 8;
      child->size_bits = (child->size_bits * f->length) % 8;
    }

    // Add the new instance or instance tree, to the root instance
    // field list.
    if_iter = if_iter == NULL ?
      append_instance_(&(root->fields), child) :
      append_instance_(&(if_iter->next_), child);

    // Note that we are changing the value of the input parameters here
    // this is part of the 'return value' of the function. Wrap the
    // bit size mod 8.
    *sbytes = *sbytes + child->size_bytes + (*sbits + child->size_bits) / 8;
    *sbits = (*sbits + child->size_bits) % 8;

    tf_iter = tf_iter->next_;
  }
}

instance_t* make_byte_instance_(field_t* field, size_t* obytes, size_t* obits) {
  instance_t* i = malloc(sizeof(instance_t));
  i->field = field;
  i->fields = NULL;
  i->offset_bytes = *obytes;
  i->offset_bits = *obits;
  i->size_bytes = field->length;
  i->size_bits = 0;
  *obytes += i->size_bytes;
  // *obits += 0;
  return i;
}

instance_t* make_bit_instance_(field_t* field, size_t* obytes, size_t* obits) {
  instance_t* i = malloc(sizeof(instance_t));
  i->field = field;
  i->fields = NULL;
  i->offset_bytes = *obytes;
  i->offset_bits = *obits;

  // Bits % 8, overflow in bytes field
  i->size_bytes = field->length / 8;
  i->size_bits = field->length % 8;
  *obytes += i->size_bytes + (*obits + i->size_bits) / 8;
  *obits = (*obits + i->size_bits) % 8;
  return i;
}

instance_t* make_composite_instance_(field_t* field,
                                     size_t* obytes, size_t* obits) {
  instance_t* i = malloc(sizeof(instance_t));
  i->field = field;
  i->fields = NULL;
  i->offset_bytes = *obytes;
  i->offset_bits = *obits;

  // The size will be updated later, after recusion into all child fields.
  i->size_bytes = 0;
  i->size_bits = 0;
  return i;
}

instance_list_t* append_instance_(instance_list_t** end_ptr,
                                  instance_t* new_field) {
  instance_list_t* il = (instance_list_t*) malloc(sizeof(instance_list_t));
  il->instance = new_field;
  il->next_ = NULL;
  *end_ptr = il;
  return il;
}

// Forward declaration
void print_instance_tree_(instance_t* i, int indent);
void print_instance_(instance_t* i);


void print_instance_tree() {
  if (instance_root == NULL) {
    printf("No specification loaded.\n");
    return;
  }

  printf("[%zu, %zu] [%zu, %zu]  --  .  (root)\n",
         instance_root->offset_bytes,
         instance_root->offset_bits,
         instance_root->size_bytes,
         instance_root->size_bits);

  print_instance_tree_(instance_root, 1);
}

void print_instance_tree_(instance_t* root, int indent) {

  // For each field of the root instance
  instance_list_t* il = root->fields;
  while(il) {

    // Indent
    int count = (indent - 1) * 2;
    while(count--)
      printf(" ");
    printf("+- ");

    // Print instance field
    instance_t* inst = il->instance;
    print_instance_(inst);
    if (inst->field->type->composite_extras != NULL)
      print_instance_tree_(inst, indent + 1);

    il = il->next_;
  }
}

void print_instance_(instance_t* i) {
    if (i->field->type == &byte_type) {
      printf("[%zu, %zu] [%zu, %zu]  --  Byte[%zu]  %s\n",
             i->offset_bytes,
             i->offset_bits,
             i->size_bytes,
             i->size_bits,
             i->field->length,
             i->field->name);
    }
    else if (i->field->type == &bit_type) {
      printf("[%zu, %zu] [%zu, %zu]  --  Bit[%zu]  %s\n",
             i->offset_bytes,
             i->offset_bits,
             i->size_bytes,
             i->size_bits,
             i->field->length,
             i->field->name);
    }
    else {
      printf("[%zu, %zu] [%zu, %zu]  --  %s[%zu]  %s\n",
             i->offset_bytes,
             i->offset_bits,
             i->size_bytes,
             i->size_bits,
             i->field->type->composite_extras->name,
             i->field->length,
             i->field->name);
    }
}
