/* 
 * Copyright (c) 2007, 2014, Oracle and/or its affiliates. All rights reserved.
 *
 * 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; version 2 of the
 * License.
 * 
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#include "serializer.h"

#include "grtpp_util.h"


#include <libxml/xmlmemory.h>
#include <libxml/parser.h>

#include <glib.h>

#include "base/log.h"
#include "base/file_functions.h"

#define GRT_FILE_VERSION_TAG "grt_format"
#define GRT_FILE_VERSION "2.0"


#define new_node(node, name, value)\
  xmlNewTextChild(node, NULL, (xmlChar*)name, (xmlChar*)value)

#define DBL_MAX_PRECISION 64


DEFAULT_LOG_DOMAIN("serializer")

static xmlNodePtr new_int_node(xmlNodePtr node, const char *name, int value)
{
  char buffer[32];
  
  g_snprintf(buffer, sizeof(buffer), "%i", value);
  
  return new_node(node, name, buffer);
}

#define set_prop(node, name, value)\
  xmlNewProp(node, (xmlChar*)name, (xmlChar*)value)


using namespace grt;
using namespace grt::internal;


xmlDocPtr internal::Serializer::create_xmldoc_for_value(
  const ValueRef &value,  const std::string &doctype, const std::string &docversion, bool list_objects_as_links)
{
  xmlDocPtr doc;
    
  doc= xmlNewDoc((xmlChar*)"1.0");
  doc->children= xmlNewDocRawNode(doc, NULL, (xmlChar*)"data", NULL);
  
  xmlNewProp(doc->children, (xmlChar*)GRT_FILE_VERSION_TAG, (xmlChar*)GRT_FILE_VERSION);
  
  if (!doctype.empty())
    set_prop(doc->children, "document_type", doctype.c_str());
  if (!docversion.empty())
    set_prop(doc->children, "version", docversion.c_str());
  
  serialize_value(value, doc->children, list_objects_as_links);
  
  return doc;
}


internal::Serializer::Serializer(GRT *grt)
  : _grt(grt)
{
}


static int base_xmlSaveFile(const char *filename, xmlDocPtr doc)
{
  char * local_filename;
  int result;
  FILE* file;
  
  if ((local_filename= g_filename_from_utf8(filename,-1,NULL,NULL,NULL)) == NULL)
    return -1;
  
  // Check if the file already exists and if so store under a temporary name first.
  file = base_fopen(local_filename, "r");
  
  if (file != NULL)
  {
    char *tempName= g_strdup_printf("%s.tmp", local_filename);
    
    result = xmlSaveFormatFile(tempName, doc, 1);
    fclose(file);
    
    if (result > 0)
    {
      // If saving the content was successful then delete the old file and use the new one.
      base_remove(local_filename);
      base_rename(tempName, local_filename);
    };
    g_free(tempName);
  }
  else
    result= xmlSaveFormatFile(local_filename,doc,1);
  
  g_free(local_filename);
  
  return result;
}

/**
 ****************************************************************************
 * @brief Stores a GRT value to a file
 * 
 *   This will serialize the value to XML and store it in a file that can
 * later be retrieved with base_grt_retrieve_from_file.
 * NOTE: This function is not reentrant.
 *
 * @param value the GRT value to store
 * @param filename name of file to store data
 * @param type document format type
 * @param version version of document format
 *
 ****************************************************************************/
void internal::Serializer::save_to_xml(const ValueRef &value, const std::string &path, 
                             const std::string &doctype, const std::string &docversion,
                             bool list_objects_as_links)
{
  xmlDocPtr doc;
  
  doc= create_xmldoc_for_value(value, doctype, docversion, list_objects_as_links);

  if (base_xmlSaveFile(path.c_str(), doc) == -1)
  {
    xmlFreeDoc(doc);
    throw std::runtime_error("Could not save XML data to file "+path);
  }
  xmlFreeDoc(doc);
}



bool internal::Serializer::seen(const ValueRef &value)
{
  void *ptr= value.valueptr();

  if (_cache.find(ptr) != _cache.end())
    return true;
  
  // value is not yet in XML tree
  _cache.insert(ptr);
  
  return false;
}


static void format_real_value(char buffer[], size_t buffer_size, double pvalue)
{
  char val_format[8];
  char* symbol= NULL;
  char* e_part= NULL;
  size_t e_part_len= 0;
  size_t n= 0;
  
  if (buffer_size > DBL_MAX_PRECISION*sizeof(char)+strlen("1.e+000\n"))
  {
    char *c;
    g_snprintf(val_format, sizeof(val_format), "%%.%ie", DBL_MAX_PRECISION);
    g_snprintf(buffer, (gulong)buffer_size, val_format, pvalue);
    symbol= buffer+strlen(buffer);
    while ('e' != *--symbol);
    e_part= symbol--;
    while ('0' == *--symbol);
    ++symbol;
    e_part_len= strlen(e_part);
    for (n= 0; n<=e_part_len; n++)
      symbol[n]= e_part[n];
    
    // replace decimal , with . for locale variations
    c= strchr(buffer, ',');
    if (c)
      *c= '.';
  }
}

/*
 *****************************************************************************
 * @brief Encodes a GRT value (and its sub-values) to a XML tree. (internal)
 * 
 * The implementation will store simple values directly and complex values
 * (lists, dicts and objects) directly at the first reference and as
 * links on further ones.
 * 
 * @param grt
 * @param parent the parent node where this value should be attached to
 * @param value the value to serialize
 * @param list_objects_as_links if this is true, the value is saved as a link
 * 
 *****************************************************************************
 */
xmlNodePtr internal::Serializer::serialize_value(const ValueRef &value, xmlNodePtr parent, bool list_objects_as_links)
{
  char buffer[100];
  xmlNodePtr node= NULL;
  
  switch (value.type())
  {
    case IntegerType:
      node= new_int_node(parent, "value", (int)*IntegerRef::cast_from(value));
      
      set_prop(node, "type", "int");
      break;
    case DoubleType:
      format_real_value(buffer, sizeof(buffer), *DoubleRef::cast_from(value));
      node= new_node(parent, "value", buffer);
      
      set_prop(node, "type", "real");
      break;
    case StringType:
      node= new_node(parent, "value", StringRef::cast_from(value).c_str());

      set_prop(node, "type", "string");
      break;
    case ListType:
    {
      BaseListRef list(BaseListRef::cast_from(value));
      
      if (seen(value))
      {
        log_debug3("found duplicate list value");
        g_snprintf(buffer, sizeof(buffer), "%p", list.valueptr());
        node= new_node(parent, "link", buffer);
        set_prop(node, "type", "list");
        return node;
      }
      
      node= new_node(parent, "value", NULL);
      
      g_snprintf(buffer, sizeof(buffer), "%p", list.valueptr());
      set_prop(node, "_ptr_", buffer);
      set_prop(node, "type", "list");
      set_prop(node, "content-type", type_to_str(list.content_type()).c_str());
      
      if (!list.content_class_name().empty())
        set_prop(node, "content-struct-name", list.content_class_name().c_str());
      
      // check if the list is part of a struct and has no 'owned' set,
      // it should serialize the contents as links
      for (size_t c= list.count(), i= 0; i < c; i++)
      {
        ValueRef cvalue(list.get(i));
        
        if (cvalue.is_valid())
        {          
          if (list_objects_as_links && cvalue.type() == ObjectType)
          {
            xmlNodePtr child= new_node(node, "link", ObjectRef::cast_from(cvalue).id().c_str());
            set_prop(child, "type", "object");
          }
          else
            serialize_value(cvalue, node, false);
        }
        else
        {
          new_node(node, "null", NULL);
        }
      }
      break;
    }

    case DictType:
      {
        DictRef dict(DictRef::cast_from(value));

        if (seen(value))
        {
          g_warning("found duplicate dict value");
          g_snprintf(buffer, sizeof(buffer), "%p", value.valueptr());
          node= new_node(parent, "link", buffer);
          set_prop(node, "type", "dict");
          return node;
        }
        
        node= new_node(parent, "value", NULL);
        
        g_snprintf(buffer, sizeof(buffer), "%p", value.valueptr());
        set_prop(node, "_ptr_", buffer);
        set_prop(node, "type", "dict");
        
        for (Dict::const_iterator iter= dict.begin(); iter != dict.end(); ++iter)
        {
          xmlNodePtr child;
          std::string k(iter->first);
          ValueRef v(iter->second);

          if (v.is_valid())
          {
            child= serialize_value(v, node, false);
            set_prop(child, "key", k.c_str());
          }
        }
      }
      break;

    case ObjectType:
    {
      ObjectRef object(ObjectRef::cast_from(value));
      
      if (!seen(object)) // owned_objects)
        node= serialize_object(object, parent);
      else
      {
        node= new_node(parent, "link", object->id().c_str());
        if (node)
        {
          set_prop(node, "type", "object");
          set_prop(node, "struct-name", object->class_name().c_str());
        }
        return node;
      }
      break;
    }

    case UnknownType:
      break;
  }
  return node;
}



bool internal::Serializer::serialize_member(const MetaClass::Member *member, const ObjectRef &object, xmlNodePtr node)
{
  std::string k= member->name;
  ValueRef v;

  // don't serialize calculated values
  if (member->calculated)
    return true;
  
  xmlNodePtr child;

  v= object->get_member(k);
  
  if (v.is_valid())
  {
    // if 'owned' for this member is not set to 1, then we just dump
    // a link instead of the whole object
    bool owned= member->owned_object;
    
    // 'owned' can be set for objects or lists, 
    // if its set on a list the *contents* will be saved as links
    if (!owned && v.type() == ObjectType)
    {
      // dontfollow is set in the struct, so just skip this member
      // 
      child= new_node(node, "link", ObjectRef::cast_from(v)->id().c_str());
      set_prop(child, "type", "object");
      set_prop(child, "struct-name", member->type.base.object_class.c_str());
    }
    else
      child= serialize_value(v, node, !owned);
    set_prop(child, "key", k.c_str());
  }
  return true;
}


xmlNodePtr internal::Serializer::serialize_object(const ObjectRef &object, xmlNodePtr parent)
{
  xmlNodePtr node;
  char checksum[40];
  
  node= new_node(parent, "value", NULL);
  set_prop(node, "type", "object");
  set_prop(node, "struct-name", object->class_name().c_str());
  set_prop(node, "id", object->id().c_str());

  g_snprintf(checksum, sizeof(checksum), "0x%x", object.get_metaclass()->crc32());

  set_prop(node, "struct-checksum", checksum);

  MetaClass *stru= object->get_metaclass();
  
  stru->foreach_member(boost::bind(&Serializer::serialize_member, this, _1, object, node));
  
  return node;
}



std::string internal::Serializer::serialize_to_xmldata(
  const ValueRef &value, const std::string &type, const std::string &version, bool list_objects_as_links)
{
  xmlDocPtr doc;
  xmlChar *buffer= NULL;
  int size;

  if (value.is_valid())
  {
    std::string tmp;

    doc= create_xmldoc_for_value(value, type, version, list_objects_as_links);

    xmlDocDumpFormatMemory(doc, &buffer, &size, 1);

    xmlFreeDoc(doc);
    tmp= (char*)buffer;
    
    xmlFree(buffer);

    return tmp;
  }
  else
    return "";
}
