/*
 * Level.cpp
 *
 * Copyright (C) 2009 Matthias Schiffer <matthias@gamezock.de>
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "Level.h"
#include "Texture.h"
#include <libxml/tree.h>
#include <libxml/valid.h>

#include <iostream>

namespace Zoom {

boost::shared_ptr<Level> Level::loadLevel(const std::string &filename) {
  boost::shared_ptr<Level> level;

  xmlDocPtr doc = xmlParseFile(("levels/"+filename).c_str());

  if(doc) {
    if(validateLevel(doc)) {
      xmlNodePtr root = xmlDocGetRootElement(doc);
      if(root && !xmlStrcmp(root->name, (xmlChar*)"level")) {
        level = boost::shared_ptr<Level>(new Level);

        for(xmlNodePtr node = root->children; node != 0; node = node->next) {
          if(node->type != XML_ELEMENT_NODE)
            continue;

          if(!xmlStrcmp(node->name, (xmlChar*)"info")) {
            level->loadLevelInfo(node);
          }
          else if(!xmlStrcmp(node->name, (xmlChar*)"rooms")) {
            level->loadRooms(node);
          }
          else if(!xmlStrcmp(node->name, (xmlChar*)"textures")) {
            level->loadTextures(node);
          }
        }

        for(std::list<Room>::iterator room = level->rooms.begin(); room != level->rooms.end(); ++room) {
          for(std::list<TriangleRecord>::iterator wall = room->walls.begin(); wall != room->walls.end(); ++wall) {
            boost::shared_ptr<WallData> wallData = boost::dynamic_pointer_cast<WallData>(wall->getData());

            if(!wallData->texture.empty()) {
              std::map<std::string, unsigned>::iterator texture = level->textures.find(wallData->texture);

              if(texture != level->textures.end())
                wall->getTriangle().setTexture(texture->second);
            }
          }
        }
      }
    }

    xmlFreeDoc(doc);
  }

  xmlCleanupParser();

  return level;
}

void Level::loadLevelInfo(xmlNodePtr infoNode) {
  for(xmlNodePtr node = infoNode->children; node != 0; node = node->next) {
    if(node->type != XML_ELEMENT_NODE)
      continue;

    if(!xmlStrcmp(node->name, (xmlChar*)"name")) {
      xmlChar *data = xmlNodeGetContent(node);
      name = (char*)data;
      xmlFree(data);
    }
    else if(!xmlStrcmp(node->name, (xmlChar*)"desc")) {
      xmlChar *data = xmlNodeGetContent(node);
      description = (char*)data;
      xmlFree(data);
    }
    else if(!xmlStrcmp(node->name, (xmlChar*)"start")) {
      startPoint = loadVector(node);
    }
  }
}

void Level::loadRooms(xmlNodePtr roomsNode) {
  for(xmlNodePtr node = roomsNode->children; node != 0; node = node->next) {
    if(node->type == XML_ELEMENT_NODE && !xmlStrcmp(node->name, (xmlChar*)"room")) {
      loadRoom(node);
    }
  }
}

void Level::loadRoom(xmlNodePtr roomNode) {
  rooms.push_back(Room());

  xmlChar *data = xmlGetProp(roomNode, (xmlChar*)"id");
  if(data) {
    rooms.back().id = (char*)data;

    xmlFree(data);
  }

  for(xmlNodePtr node = roomNode->children; node != 0; node = node->next) {
    if(node->type == XML_ELEMENT_NODE && !xmlStrcmp(node->name, (xmlChar*)"triangle")) {
      rooms.back().walls.push_back(loadWall(node));
    }
  }
}

void Level::loadTextures(xmlNodePtr texturesNode) {
  for(xmlNodePtr node = texturesNode->children; node != 0; node = node->next) {
    if(node->type == XML_ELEMENT_NODE && !xmlStrcmp(node->name, (xmlChar*)"texture")) {
      std::string id, name;

      xmlChar *data = xmlGetProp(node, (xmlChar*)"id");
      if(data) {
        id = (char*)data;
        xmlFree(data);
      }

      data = xmlGetProp(node, (xmlChar*)"name");
      if(data) {
        name = (char*)data;
        xmlFree(data);
      }

      if(!id.empty() && !name.empty()) {
        unsigned texture = Texture::loadTexture(name);

        if(texture) {
          textures.insert(std::make_pair(id, texture));
        }
      }
    }
  }
}

TriangleRecord Level::loadWall(xmlNodePtr wallNode) {
  boost::shared_ptr<WallData> wallData(new WallData);

  TriangleRecord wall;
  wall.setData(wallData);


  xmlChar *data = xmlGetProp(wallNode, (xmlChar*)"texture");
  if(data) {
    wallData->texture = (char*)data;
    xmlFree(data);
  }

  int vertexNum = -1;

  for(xmlNodePtr node = wallNode->children; node != 0; node = node->next) {
    if(node->type != XML_ELEMENT_NODE)
      continue;

    if(!xmlStrcmp(node->name, (xmlChar*)"vertex")) {
      if(++vertexNum > 2)
        break;

      wall.getTriangle().setVertex(vertexNum, loadVector(node));
    }
    else if(!xmlStrcmp(node->name, (xmlChar*)"normal")) {
      if(vertexNum < 0)
        continue;

      wall.getTriangle().setNormal(vertexNum, loadVector(node));
    }
    else if(!xmlStrcmp(node->name, (xmlChar*)"texcoords")) {
      if(vertexNum < 0) continue;

      data = xmlGetProp(node, (xmlChar*)"s");
      if(data) {
        wall.getTriangle().getTexCoords(vertexNum)[0] = atof((char*)data);
        xmlFree(data);
      }

      data = xmlGetProp(node, (xmlChar*)"t");
      if(data) {
        wall.getTriangle().getTexCoords(vertexNum)[1] = atof((char*)data);
        xmlFree(data);
      }
    }
  }

  vmml::vec3f normal = wall.getTriangle().computeNormal();

  if(normal.squared_length() > 0) {
    normal.normalize();

    for(int i = 0; i < 3; ++i) {
      if(wall.getTriangle().getNormal(i).squared_length() == 0)
        wall.getTriangle().setNormal(i, normal);
    }
  }


  return wall;
}

vmml::vec3f Level::loadVector(xmlNodePtr node) {
  vmml::vec3f ret(vmml::vec3f::ZERO);

  xmlChar *data = xmlGetProp(node, (xmlChar*)"x");
  if(data) {
    ret.x() = atof((char*)data);
    xmlFree(data);
  }

  data = xmlGetProp(node, (xmlChar*)"y");
  if(data) {
    ret.y() = atof((char*)data);
    xmlFree(data);
  }

  data = xmlGetProp(node, (xmlChar*)"z");
  if(data) {
    ret.z() = atof((char*)data);
    xmlFree(data);
  }

  return ret;
}

bool Level::validateLevel(xmlDocPtr doc) {
  bool ret = false;

  xmlDtdPtr dtd = xmlParseDTD((xmlChar*)"-//libzoom//DTD level 0.1//EN", (xmlChar*)"levels/level.dtd");

  if(dtd) {
    xmlValidCtxtPtr validCtxt = xmlNewValidCtxt();

    if(validCtxt) {
      if(xmlValidateDtd(validCtxt, doc, dtd))
        ret = true;

      xmlFreeValidCtxt(validCtxt);
    }

    xmlFreeDtd(dtd);
  }

  return ret;
}

}
