/**
 * Copyright (c) 2021 Dominic Masters
 * 
 * This software is released under the MIT License.
 * https://opensource.org/licenses/MIT
 */

#include "xml.h"

int32_t xmlLoadChild(xml_t *xml, char *data, int32_t i) {
  char c;
  int32_t level = 0;
  uint8_t doing = XML_DOING_NOTHING;
  bool insideTag = false;
  char* buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
  int32_t bufferLength = 0;

  xml->value = NULL;
  xml->attributeCount = 0;

  xml->children = malloc(sizeof(xml_t) * XML_CHILD_COUNT_MAX);
  xml->childrenCount = 0;

  while(c = data[i++]) {
    switch(doing) {
      case XML_DOING_NOTHING:
        // Look for either an opening tag (<) or a word for a value.
        if(c == '>') continue;
        if(c == '<') {
          if(insideTag) {
            i = xmlLoadChild(xml->children + xml->childrenCount++, data, i-1);
            doing = XML_PARSING_CHILD;
          } else {
            doing = XML_PARSING_TAG_NAME;
            level++;
            insideTag = true;
          }
          continue;
        }

        if(xmlIsWhitespace(c)) continue;
        doing = XML_PARSING_VALUE;
        buffer[bufferLength++] = c;
        break;
      
      case XML_PARSING_TAG_NAME:
        // Just keep reading until we either hit a space (end of the tag name)
        // or a closing tag value, either / or >
        if(xmlIsWhitespace(c) || c == '>' || c == '/') {
          buffer[bufferLength] = '\0';
          xml->node = buffer;
          buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
          bufferLength = 0;
          if(c == '/') {
            level--;
            insideTag = false;
            doing = XML_PARSING_CLOSE;
          } else {
            doing = c == '>' ? XML_DOING_NOTHING : XML_LOOKING_FOR_ATTRIBUTE;
          }
          continue;
        }
        buffer[bufferLength++] = c;
        break;
      
      case XML_LOOKING_FOR_ATTRIBUTE:
        // Look until we hit either the end of a tag, or the attribute itself
        if(xmlIsWhitespace(c) || c == '>' || c == '/' || c == '=') {
          if(c == '>' || c == '/') {
            doing = XML_DOING_NOTHING;
            if(c == '/') {
              level--;
              insideTag = false;
              doing = XML_PARSING_CLOSE;
            }
          } else if(c == '=') {
            doing = XML_LOOKING_FOR_ATTRIBUTE_VALUE;
          } else {
            doing = XML_LOOKING_FOR_ATTRIBUTE;
          }

          if(bufferLength > 0) {
            buffer[bufferLength] = '\0';
            xml->attributeNames[xml->attributeCount++] = buffer;
            xml->attributeDatas[xml->attributeCount] = NULL;
            buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
            bufferLength = 0;
          }
          continue;
        }

        buffer[bufferLength++] = c;
        break;

      case XML_LOOKING_FOR_ATTRIBUTE_VALUE:
        // Keep looking until we find a quote mark
        if(xmlIsWhitespace(c)) continue;
        if(c == '>' || c == '/') {
          doing = XML_DOING_NOTHING;
          insideTag = false;
          continue;
        }

        if(c != '"') continue;
        doing = XML_PARSING_ATTRIBUTE_VALUE;
        break;

      case XML_PARSING_ATTRIBUTE_VALUE:
        // Parse the attribute value until we find a quote mark.
        if(c == '"') {
          doing = XML_LOOKING_FOR_ATTRIBUTE;
          buffer[bufferLength] = '\0';
          xml->attributeDatas[xml->attributeCount - 1] = buffer;
          buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
          bufferLength = 0;
          continue;
        }

        buffer[bufferLength++] = c;
        break;

      case XML_PARSING_VALUE:
        // Keep parsing child until we find a < for an opening/closing tag.
        if(c == '<') {
          // In HTML Spec there could be a child here but not in XML spec.
          doing = XML_PARSING_CLOSE;
          buffer[bufferLength] = '\0';
          bufferLength = 0;
          xml->value = buffer;
          buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
          continue;
        }

        buffer[bufferLength++] = c;
        break;
        
      case XML_PARSING_CHILD:
        if(c == '<') {
          // Read ahead and confirm this is a close or not
          if(data[i] == '/') {
            doing = XML_PARSING_CLOSE;
            continue;
          }

          // Likely another child.
          i = xmlLoadChild(xml->children + xml->childrenCount++, data, i-1);
        }

        if(xmlIsWhitespace(c)) continue;

        // In HTML Spec there's a chance for there to be a value here, but not
        // in the XML spec.
        break;

      case XML_PARSING_CLOSE:
        // Just keep parsing until the tag closer finishes.
        if(c != '>') continue;
        doing = XML_DOING_NOTHING;
        
        //TODO: Return index or something?
        free(buffer);
        return i;

      default:
        break;
    }
  }

  free(buffer);
  return i;
}

void xmlLoad(xml_t *xml, char *data) {
  xmlLoadChild(xml, data, 0);
}

void xmlDispose(xml_t *xml) {
  uint8_t i;

  // Dispose children recursively
  for(i = 0; i < xml->childrenCount; i++) {
    xmlDispose(xml->children + i);
  }

  // Free children array.
  free(xml->children);

  // Dispose attributes
  for(i = 0; i < xml->attributeCount; i++) {
    free(xml->attributeNames[i]);
    if((xml->attributeDatas + i) != NULL) {
      free(xml->attributeDatas[i]);
    }
  }
  
  free(xml->node);
  if(xml-> value != NULL) free(xml->value);
}

int16_t xmlGetAttributeByName(xml_t *xml, char *name) {
  int16_t i;
  for(i = 0; i < xml->attributeCount; i++) {
    if(strcmp(xml->attributeNames[i], name) == 0) return i;
  }
  return -1;
}

bool xmlIsWhitespace(char c) {
  return c == ' ' || c == '\r' || c == '\n' || c == '\t';
}