/* The opening and saving functions.

   Copyright (C) 2012 Ian Dunn.

   This program 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.

   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#include <libxml/xmlwriter.h>
#include <libxml/xmlreader.h>

#include "aio.h"
#include "var.h"
#include "sen-data.h"
#include "proof.h"
#include "list.h"
#include "rules.h"
#include "process.h"

#define XML_ERR(r) {fprintf (stderr, "XML Error\n"); REPORT (); return r;}
#define PRINT_LINE() {printf ("%i\n", __LINE__);}

/* Gets the first attribute from an xml stream.
 *  input:
 *    xml - the xml stream to get the attribute from.
 *    data - the data to compare the tag against.
 *  output:
 *    the attribute data.
 */
static xmlChar *
aio_get_first_attribute (xmlTextReader * xml, const char * data)
{
  int ret;
  xmlChar * buffer;

  ret = xmlTextReaderMoveToFirstAttribute (xml);
  if (ret < 0)
    return NULL;

  buffer = xmlTextReaderName (xml);
  if (!buffer)
    return NULL;

  if (strcmp ((const char *) buffer, data))
    return NULL;

  free (buffer);

  buffer = xmlTextReaderValue (xml);
  return buffer;
}

/* Gets the next attribute from an xml stream.
 *  input:
 *    xml - the xml stream to get the attribute from.
 *    data - the data to compare the tag against.
 *  output:
 *    the attribute data.
 */
static xmlChar *
aio_get_next_attribute (xmlTextReader * xml, const char * data)
{
  int ret;
  xmlChar * buffer;

  ret = xmlTextReaderMoveToNextAttribute (xml);
  if (ret < 0)
    return NULL;

  buffer = xmlTextReaderName (xml);
  if (!buffer)
    return NULL;

  if (strcmp ((const char *) buffer, data))
    return NULL;

  buffer = xmlTextReaderValue (xml);
  return buffer;
}

/* Saves a proof to a file.
 *  input:
 *    proof - the proof to save.
 *    file_name - the name of the file to save to.
 *  output:
 *    0 on success, -1 on error.
 */
int
aio_save (proof_t * proof, const char * file_name)
{
  xmlTextWriter * xml;
  int ret;
  item_t * itr;

  xml = xmlNewTextWriterFilename (file_name, 0);
  if (!xml)
    {
      XML_ERR (-1);
    }

  ret = xmlTextWriterSetIndent (xml, 1);
  if (ret < 0)
    {
      XML_ERR (-1);
    }

  ret = xmlTextWriterStartDocument (xml, NULL, NULL, NULL);
  if (ret < 0)
    {
      XML_ERR (-1);
    }

  ret = xmlTextWriterStartElement (xml, XML_CAST PROOF_TAG);
  if (ret < 0)
    {
      XML_ERR (-1);
    }

  char * mode;

  if (proof->boolean)
    mode = "boolean";
  else
    mode = "standard";

  ret = xmlTextWriterWriteFormatAttribute (xml, XML_CAST MODE_DATA,
					   "%s", mode);

  ret = xmlTextWriterStartElement (xml, XML_CAST GOAL_TAG);
  if (ret < 0)
    {
      XML_ERR (-1);
    }

  itr = proof->goals->head;

  for (itr = proof->goals->head; itr != NULL; itr = itr->next)
    {
      unsigned char * text = itr->value;
      ret = xmlTextWriterStartElement (xml, XML_CAST GOAL_ENTRY);
      if (ret < 0)
	{
	  XML_ERR (-1);
	}

      ret = xmlTextWriterWriteFormatAttribute (xml, XML_CAST TEXT_DATA,
					       "%s", text);
      if (ret < 0)
	{
	  XML_ERR (-1);
	}

      ret = xmlTextWriterEndElement (xml);
      if (ret < 0)
	{
	  XML_ERR (-1);
	}
    }

  // End <variables> tag.
  ret = xmlTextWriterEndElement (xml);
  if (ret < 0)
    {
      XML_ERR (-1);
    }

  // Begin <premises> tag.
  ret = xmlTextWriterStartElement (xml, XML_CAST PREMISE_TAG);
  if (ret < 0)
    {
      XML_ERR (-1);
    }

  for (itr = proof->everything->head; itr != NULL; itr = itr->next)
    {
      // Write each of the premises.
      sen_data * sd = itr->value;
      if (!sd->premise)
	break;

      ret = xmlTextWriterStartElement (xml, XML_CAST SENTENCE_ENTRY);
      if (ret < 0)
	{
	  XML_ERR (-1);
	}

      // Write the line number.
      ret = xmlTextWriterWriteFormatAttribute (xml, XML_CAST LINE_DATA,
					       "%i", sd->line_num);
      if (ret < 0)
	{
	  XML_ERR (-1);
	}

      ret = xmlTextWriterWriteFormatAttribute (xml, XML_CAST TEXT_DATA,
					       "%s", sd->text);
      if (ret < 0)
	{
	  XML_ERR (-1);
	}

      
      ret = xmlTextWriterEndElement (xml);
      if (ret < 0)
	{
	  XML_ERR (-1);
	}
    }

  ret = xmlTextWriterEndElement (xml);
  if (ret < 0)
    {
      XML_ERR (-1);
    }

  // Begin <conclusions> tag.
  ret = xmlTextWriterStartElement (xml, XML_CAST CONCLUSION_TAG);
  if (ret < 0)
    {
      XML_ERR (-1);
    }

  for (; itr != NULL; itr = itr->next)
    {
      // Wriet each of the conclusions.
      sen_data * sd = itr->value;
      char * refs;
      int i = 0, num_refs = 0, ref_off = 0, max_line = 0;

      ret = xmlTextWriterStartElement (xml, XML_CAST SENTENCE_ENTRY);
      if (ret < 0)
	{
	  XML_ERR (-1);
	}
      ret = xmlTextWriterWriteFormatAttribute (xml, XML_CAST LINE_DATA,
					       "%i", sd->line_num);
      if (ret < 0)
	{
	  XML_ERR (-1);
	}
      ret = xmlTextWriterWriteFormatAttribute (xml, XML_CAST RULE_DATA,
					       "%i", sd->rule);
      if (ret < 0)
	{
	  XML_ERR (-1);
	}

      while (sd->refs[i] != -1)
	{
	  max_line = (max_line > sd->refs[i]) ? max_line : sd->refs[i];
	  num_refs++;
	  i++;
	}

      max_line = (int) log10 (max_line) + 1;

      refs = (char *) calloc (num_refs * (max_line + 1), sizeof (char));
      if (!refs)
	{
	  REPORT ();
	  perror (NULL);
	  return -1;
	}

      i = 0;

      while (sd->refs[i] != -1)
	{
	  int ref_line = sd->refs[i];
	  ref_off += sprintf (refs + ref_off, "%i", ref_line);

	  if (sd->refs[i+1] != -1)
	    ref_off += sprintf (refs + ref_off, ",");
	  i++;
	}

      ret = xmlTextWriterWriteAttribute (xml, XML_CAST REF_DATA,
					 XML_CAST refs);
      if (ret < 0)
	{
	  XML_ERR (-1);
	}

      ret = xmlTextWriterWriteFormatAttribute (xml, XML_CAST DEPTH_DATA,
					       "%i", sd->depth);
      if (ret < 0)
	{
	  XML_ERR (-1);
	}

      // If there is a file, write it.
      if (sd->file)
	{
	  ret = xmlTextWriterWriteFormatAttribute (xml, XML_CAST FILE_DATA,
					     "%s", sd->file);
	  if (ret < 0)
	    {
	      XML_ERR (-1);
	    }
	}

      ret = xmlTextWriterWriteFormatAttribute (xml, XML_CAST TEXT_DATA,
					       "%s", sd->text);
      if (ret < 0)
	{
	  XML_ERR (-1);
	}

      ret = xmlTextWriterEndElement (xml);
      if (ret < 0)
	{
	  XML_ERR (-1);
	}
    }

  // End <conclusions> tag.
  ret = xmlTextWriterEndElement (xml);
  if (ret < 0)
    {
      XML_ERR (-1);
    }

  // End <proof> tag.
  ret = xmlTextWriterEndElement (xml);
  if (ret < 0)
    {
      XML_ERR (-1);
    }

  // End the document.
  ret = xmlTextWriterEndDocument (xml);
  if (ret < 0)
    {
      XML_ERR (-1);
    }

  xmlFreeTextWriter (xml);

  return 0;
}

/* Opens a proof.
 *  input:
 *    file_name - the name of the file to open.
 *  output:
 *    the opened proof, or NULL on error.
 */
proof_t *
aio_open (const char * file_name)
{
  if (file_name == NULL)
    return NULL;

  proof_t * proof;
  xmlTextReader * xml;

  proof = proof_init ();
  if (!proof)
    return NULL;

  xml = xmlReaderForFile (file_name, NULL, 0);
  if (!xml)
    {
      XML_ERR (NULL);
    }

  xmlChar * buffer;
  int ret;
  int depth;

  ret = xmlTextReaderRead (xml);

  if (ret < 0)
    {
      XML_ERR (NULL);
    }

  buffer = xmlTextReaderName (xml);
  if (!buffer)
    {
      XML_ERR (NULL);
    }

  if (strcmp ((const char *) buffer, PROOF_TAG))
    {
      XML_ERR (NULL);
    }

  free (buffer);

  buffer = aio_get_first_attribute (xml, MODE_DATA);
  if (buffer)
    {
      if (!strcmp (buffer, "boolean"))
	proof->boolean = 1;
    }

  // Get the <goals> tag.
  ret = xmlTextReaderRead (xml);
  if (ret < 0)
    {
      XML_ERR (NULL);
    }

  ret = xmlTextReaderRead (xml);
  if (ret < 0)
    {
      XML_ERR (NULL);
    }

  buffer = xmlTextReaderName (xml);
  if (!buffer)
    {
      XML_ERR (NULL);
    }

  if (strcmp ((const char *) buffer, GOAL_TAG))
    {
      XML_ERR (NULL);
    }

  free (buffer);

  ret = xmlTextReaderRead (xml);
  if (ret < 0)
    {
      XML_ERR (NULL);
    }

  depth = xmlTextReaderDepth (xml);
  if (depth < 0)
    {
      XML_ERR (NULL);
    }

  // Read the goals.

  if (depth == 2)
    {
      while (ret == 1)
	{
	  ret = xmlTextReaderRead (xml);
	  if (ret < 0)
	    {
	      XML_ERR (NULL);
	    }

	  buffer = xmlTextReaderName (xml);
	  if (!buffer)
	    {
	      XML_ERR (NULL);
	    }

	  if (!strcmp ((const char *) buffer, GOAL_ENTRY))
	    {
	      buffer = aio_get_first_attribute (xml, TEXT_DATA);
	      if (!buffer)
		{
		  XML_ERR (NULL);
		}

	      item_t * ret_itm;
	      ret_itm = ls_push_obj (proof->goals, buffer);
	      if (!ret_itm)
		return NULL;
	    }
	  else if (!strcmp ((const char *) buffer, GOAL_TAG))
	    {
	      ret = xmlTextReaderRead (xml);
	      if (ret < 0)
		{
		  XML_ERR (NULL);
		}
	      break;
	    }
	  else
	    {
	      XML_ERR (NULL);
	    }

	  ret = xmlTextReaderRead (xml);
	  if (ret < 0)
	    {
	      XML_ERR (NULL);
	    }
	}
    }

  int line = 1;

  ret = xmlTextReaderRead (xml);
  if (ret < 0)
    {
      XML_ERR (NULL);
    }

  buffer = xmlTextReaderName (xml);
  if (!buffer)
    {
      XML_ERR (NULL);
    }

  if (strcmp ((const char *) buffer, PREMISE_TAG))
    {
      XML_ERR (NULL);
    }

  free (buffer);

  ret = xmlTextReaderRead (xml);
  if (ret < 0)
    {
      XML_ERR (NULL);
    }

  while (ret == 1)
    {
      ret = xmlTextReaderRead (xml);
      if (ret < 0)
	{
	  XML_ERR (NULL);
	}

      buffer = xmlTextReaderName (xml);
      if (!buffer)
	{
	  XML_ERR (NULL);
	}

      if (!strcmp ((const char *) buffer, SENTENCE_ENTRY))
	{
	  sen_data * sd;
	  int line_num;

	  buffer = aio_get_first_attribute (xml, LINE_DATA);
	  if (!buffer)
	    {
	      XML_ERR (NULL);
	    }

	  ret = sscanf ((const char *) buffer, "%i", &line_num);
	  if (ret != 1)
	    {
	      XML_ERR (NULL);
	    }

	  free (buffer);

	  buffer = aio_get_next_attribute (xml, TEXT_DATA);
	  if (!buffer)
	    {
	      XML_ERR (NULL);
	    }

	  sd = sen_data_init (line++, -1, buffer, NULL, 1, NULL, 0, 0, NULL);
	  if (!sd)
	    return NULL;

	  item_t * itm;
	  itm = ls_push_obj (proof->everything, sd);
	  if (!itm)
	    return NULL;
	}
      else if (!strcmp ((const char *) buffer, PREMISE_TAG))
	{
	  ret = xmlTextReaderRead (xml);
	  if (ret < 0)
	    {
	      XML_ERR (NULL);
	    }
	  break;
	}
      else
	{
	  XML_ERR (NULL);
	}
      ret = xmlTextReaderRead (xml);
      if (ret < 0)
	{
	  XML_ERR (NULL);
	}
    }

  ret = xmlTextReaderRead (xml);
  if (ret < 0)
    {
      XML_ERR (NULL);
    }

  buffer = xmlTextReaderName (xml);
  if (!buffer)
    {
      XML_ERR (NULL);
    }

  if (strcmp ((const char *) buffer, CONCLUSION_TAG))
    {
      XML_ERR (NULL);
    }

  free (buffer);

  ret = xmlTextReaderRead (xml);
  if (ret < 0)
    {
      XML_ERR (NULL);
    }

  depth = xmlTextReaderDepth (xml);
  if (depth < 0)
    {
      XML_ERR (NULL);
    }

  if (depth == 2)
    {
      while (1)
	{
	  ret = xmlTextReaderRead (xml);
	  if (ret < 0)
	    {
	      XML_ERR (NULL);
	    }

	  buffer = xmlTextReaderName (xml);
	  if (!buffer)
	    {
	      XML_ERR (NULL);
	    }

	  if (!strcmp ((const char *) buffer, CONCLUSION_TAG))
	    {
	      //printf ("Got Closing conclusion tag\n");
	      break;
	    }

	  if (!strcmp ((const char *) buffer, SENTENCE_ENTRY))
	    {

	      int attrs;
	      sen_data * sd;
	      int line_num, rule, * refs, i, ref_len, num_refs, sen_depth;
	      unsigned char * file;

	      attrs = xmlTextReaderAttributeCount (xml);
	      if (attrs < 0)
		{
		  XML_ERR (NULL);
		}

	      // Get the line number.

	      buffer = aio_get_first_attribute (xml, LINE_DATA);
	      if (!buffer)
		{
		  XML_ERR (NULL);
		}

	      ret = sscanf ((const char *) buffer, "%i", &line_num);
	      if (ret != 1)
		{
		  XML_ERR (NULL);
		}

	      // Get the rule index.

	      buffer = aio_get_next_attribute (xml, RULE_DATA);
	      if (!buffer)
		{
		  XML_ERR (NULL);
		}

	      ret = sscanf ((const char *) buffer, "%i", &rule);
	      if (ret != 1)
		{
		  XML_ERR (NULL);
		}

	      // Get the reference line numbers.

	      buffer = aio_get_next_attribute (xml, REF_DATA);
	      if (!buffer)
		{
		  XML_ERR (NULL);
		}

	      // Determine the number of references.

	      i = num_refs = 0;
	      ref_len = strlen ((const char *) buffer);

	      for (; i < ref_len; i++)
		if (buffer[i] == ',')
		  num_refs ++;

	      num_refs++;
	      refs = (int *) calloc (num_refs, sizeof (int));
	      if (!refs)
		{
		  REPORT ();
		  perror (NULL);
		  return NULL;
		}

	      int old_i = -1, j = 0;

	      for (i = 0; i < ref_len; i++)
		{
		  if (buffer[i] == ',')
		    {
		      ret = sscanf ((const char *) buffer + old_i + 1, "%i",
				    &(refs[j]));
		      if (ret != 1)
			{
			  XML_ERR (NULL);
			}

		      j++;
		      old_i = i;
		    }
		}

	      ret = sscanf ((const char *) buffer + old_i + 1, "%i", &(refs[j]));
	      if (ret < 0 && ret != EOF)
		{
		  XML_ERR (NULL);
		}

	      refs[j+1] = -1;

	      // Get the file if one exists.

	      buffer = aio_get_next_attribute (xml, DEPTH_DATA);
	      if (buffer)
		{
		  sscanf (buffer, "%i", &sen_depth);
		}

	      if (attrs == 6)
		{
		  buffer = aio_get_next_attribute (xml, FILE_DATA);
		  if (!buffer)
		    {
		      XML_ERR (NULL);
		    }

		  if (buffer[0] == '\0')
		    file = NULL;
		  else
		    file = buffer;
		}
	      else
		{
		  file = NULL;
		}

	      // Get the text.

	      buffer = aio_get_next_attribute (xml, TEXT_DATA);
	      if (!buffer)
		{
		  XML_ERR (NULL);
		}

	      int sub, old_depth;
	      old_depth = ((sen_data *) proof->everything->tail->value)->depth;
	      if (sen_depth > old_depth && old_depth != -1)
		{
		  sub = 1;
		}
	      else if (sen_depth <= old_depth)
		{
		  sub = 0;
		  if (sen_depth < old_depth)
		    sen_depth = -1;
		}

	      sd = sen_data_init (line++, rule, buffer, refs, 0,
				  file, sub, sen_depth, NULL);
	      if (!sd)
		{
		  XML_ERR (NULL);
		}

	      item_t * itm;
	      itm = ls_push_obj (proof->everything, sd);
	      if (!itm)
		return NULL;
	    }

	}
    }

  xmlFreeTextReader (xml);

  return proof;
}
