/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
 *            macroparser.cc
 *
 *  Wed Jun  4 11:57:38 CEST 2008
 *  Copyright 2008 Bent Bisballe Nyeng
 *  deva@aasimon.org
 ****************************************************************************/

/*
 *  This file is part of Pracro.
 *
 *  Pracro 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 2 of the License, or
 *  (at your option) any later version.
 *
 *  Pracro 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 Pracro; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 */
#include "macroparser.h"

// For assert
#include <assert.h>

// For open and friends
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

// For vprintf and friends
#include <stdarg.h>

#ifndef XML
// For XML
#include <config.h>
#endif/*XML*/

#include <errno.h>

#include "exception.h"

void MacroParser::error(const char* fmt, ...)
{
  // TODO: Throw exception here.

  fprintf(stderr, "Error in MacroParser: ");

  va_list argp;
  va_start(argp, fmt);
  vfprintf(stderr, fmt, argp);
  va_end(argp);

  fprintf(stderr, "\n");

  throw Exception("Error in MacroParser");
}

MacroParser::MacroParser(std::string macro)
{
  state = UNDEFINED;
  m = NULL;
  current_map = NULL;
  current_script = NULL;

  file = XML"/macros/" + macro + ".xml";

  printf("Using macro file: %s\n", file.c_str());

  fd = open(file.c_str(), O_RDONLY);
  if(fd == -1) error("Could not open file %s", file.c_str());
}

MacroParser::~MacroParser()
{
  if(fd != -1) close(fd);
  if(m) delete m;
}

void MacroParser::characterData(std::string &data)
{
  if(state == MAP) {
    assert(current_map); // No map present!
    current_map->attributes["lua"].append(data);
  }

  if(state == SCRIPT) {
    assert(current_script); // No script present!
    current_script->attributes["code"].append(data);
  }
}

void MacroParser::startTag(std::string name, std::map< std::string, std::string> attributes)
{
  // Create macro and enable parsing of queries, maps and window
  if(name == "macro") {
    if(state != UNDEFINED) error("macro found not root tag.");
    state = MACRO;

    assert(!m); // A Macro has already been allocated, cannot create macro!
    
    m = new Macro();
    m->attributes = attributes;

    return;
  }

  // Enable Query parsing
  if(name == "queries") {
    if(state != MACRO) error("queries found outside macro.");
    state = QUERIES;
    
    assert(m); // No macro is currently available, cannot create queries!

    return;
  }

  // Create Query
  if(name == "query") {
    if(state != QUERIES) error("query found outside queries.");
    state = QUERY;

    assert(m); // No macro is currently available, cannot create query!

    Query q;
    q.attributes = attributes;
    m->queries.push_back(q);

    return;
  }

  // Enable Map parsing
  if(name == "maps") {
    if(state != MACRO) error("maps found outside macro.");
    state = MAPS;

    assert(m); // No macro is currently available, cannot create maps!

    return;
  }

  // Create Query
  if(name == "map") {
    if(state != MAPS) error("map found outside maps.");
    state = MAP;

    assert(m); // No macro is currently available, cannot create map!

    Map map;
    map.attributes = attributes;
    m->maps.push_back(map);
    current_map = &(m->maps.back());

    return;
  }

  // Enable script parsing
  if(name == "scripts") {
    if(state != MACRO) error("scripts found outside macro.");
    state = SCRIPTS;

    assert(m); // No macro is currently available, cannot create maps!

    return;
  }

  // Create Query
  if(name == "script") {
    if(state != SCRIPTS) error("script found outside scripts.");
    state = SCRIPT;

    assert(m); // No macro is currently available, cannot create map!

    Script s;
    s.attributes = attributes;
    m->scripts.push_back(s);
    current_script = &(m->scripts.back());

    return;
  }

  // Enable widget parsing
  if(name == "window") {

    if(state != MACRO) error("window found outside macro.");
    state = WINDOW;

    assert(m); // No macro is currently available, cannot create window!
  
    m->window.attributes = attributes;
    m->window.attributes["tagname"] = name;

    Widget *current = &(m->window);
    widgetstack.push_back(current);

    return;
  }

  // TODO: We need to parse some (maybe even all) widgets in order to
  //       make db lookup of the previous values.
  if(state == WINDOW) {

    assert(widgetstack.size()); // Widget stack is empty, cannot create!

    Widget w;
    w.attributes = attributes;
    w.attributes["tagname"] = name;

    Widget *parent = widgetstack.back();
    parent->widgets.push_back(w);
    
    Widget *current = &(parent->widgets.back());
    widgetstack.push_back(current);

    return;
  }


  // Handle include
  if(name == "include") {
    return;
  }

  error("Unknown/illegal tag: %s", name.c_str());
}

void MacroParser::endTag(std::string name)
{
  if(name == "macro") {
    state = UNDEFINED;
  }
  if(name == "queries") state = MACRO;
  if(name == "query") state = QUERIES;
  if(name == "maps") state = MACRO;
  if(name == "map") {
    current_map = NULL;
    state = MAPS;
  }
  if(name == "scripts") state = MACRO;
  if(name == "script") {
    current_script = NULL;
    state = SCRIPTS;
  }
  if(name == "window") state = MACRO;
  
  if(state == WINDOW) {
    assert(widgetstack.size()); // Widget stack is empty, cannot pop!
    widgetstack.pop_back();
    if(widgetstack.size() == 0) state = MACRO;
  }
}

int MacroParser::readData(char *data, size_t size)
{
  if(fd == -1) {
    fprintf(stderr, "Invalid file descriptor.\n"); fflush(stderr);
    return 0;
  }
  ssize_t r = read(fd, data, size);
  if(r == -1) {
    printf("Could not read...%s\n", strerror(errno)); fflush(stdout);
    return 0;
  }
  return r;
}

void MacroParser::parseError(char *buf, size_t len, std::string error, int lineno)
{
  fprintf(stderr, "MacroParser[%s] error at line %d: %s\n", file.c_str(), lineno, error.c_str());
  fprintf(stderr, "\tBuffer %u bytes: [", len);
  if(fwrite(buf, len, 1, stderr) != len) {}
  fprintf(stderr, "]\n");
  fflush(stderr);
}

Macro *MacroParser::getMacro()
{
  return m;
}

#ifdef TEST_MACROPARSER

void print_attributes(std::string prefix,
                      std::map< std::string, std::string > &att)
{
  std::map< std::string, std::string >::iterator i = att.begin();
  while(i != att.end()) {
    printf("%s %s = \"%s\"\n", prefix.c_str(), (*i).first.c_str(), (*i).second.c_str());
    i++;
  }
}

int main()
{
  MacroParser parser("example");
  parser.parse();

  Macro *m = parser.getMacro();

  printf("\t\t\t[Macro]:\n");
  print_attributes("\t\t\t\t-", m->attributes);
    
  std::vector< Query >::iterator qi = m->queries.begin();
  while(qi != m->queries.end()) {
    printf("\t\t\t\t[Query]:\n");
    print_attributes("\t\t\t\t\t-", (*qi).attributes);
    qi++;
  }

  std::vector< Map >::iterator mi = m->maps.begin();
  while(mi != m->maps.end()) {
    printf("\t\t\t\t[Map]:\n");
    print_attributes("\t\t\t\t\t-", (*mi).attributes);
    mi++;
  }
  
  return 0;
}

#endif/*TEST_MACROPARSER*/