//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//=========================================================================
#include "smtk/io/ExportJSON.h"
#include "smtk/io/ExportJSON.txx"

#include "smtk/common/Version.h"

#include "smtk/model/SessionRegistrar.h"
#include "smtk/model/SessionIOJSON.h"
#include "smtk/model/Manager.h"
#include "smtk/model/Entity.h"
#include "smtk/model/Model.h"
#include "smtk/model/Operator.h"
#include "smtk/model/Tessellation.h"
#include "smtk/model/Arrangement.h"

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/Definition.h"
#include "smtk/attribute/MeshItem.h"
#include "smtk/attribute/System.h"

#include "smtk/mesh/Manager.h"
#include "smtk/mesh/Collection.h"

#include "smtk/io/AttributeWriter.h"
#include "smtk/io/WriteMesh.h"
#include "smtk/io/Logger.h"
#include "smtk/io/mesh/MeshIO.h"

#include "cJSON.h"

#include <fstream>

#include <stdlib.h> // for free()

using namespace smtk::io;
using namespace smtk::common;
using namespace smtk::model;

// Some cJSON helpers
namespace {
  cJSON* cJSON_CreateLongArray(const long* ints, unsigned count);
  cJSON* cJSON_CreateStringArray(const std::string* strings, unsigned count);
  cJSON* cJSON_CreateUUIDArray(const smtk::common::UUID* uids, unsigned count);

  cJSON* cJSON_AddAttributeSpec(
    cJSON* opEntry,
    const char* tagName, // tag holding name of attribute
    const char* xmlTagName, // tag holding XML for attribute
    smtk::attribute::AttributePtr spec)
    {
    if (spec)
      {
      smtk::attribute::System tmpSys;
      tmpSys.setRefModelManager(spec->modelManager());
      tmpSys.copyAttribute(
        spec, smtk::attribute::System::FORCE_COPY_ASSOCIATIONS);
      smtk::io::Logger log;
      smtk::io::AttributeWriter wri;
      wri.includeDefinitions(false);
      wri.includeInstances(true);
      wri.includeModelInformation(false);
      wri.includeViews(true); // now operator could specify views
      std::string xml;
      bool err = wri.writeContents(tmpSys, xml, log, true);
      if (!err)
        {
        cJSON_AddItemToObject(opEntry, tagName,
          cJSON_CreateString(spec->name().c_str()));
        cJSON_AddItemToObject(opEntry, xmlTagName,
          cJSON_CreateString(xml.c_str()));
        }
      }
    return opEntry;
    }

  cJSON* cJSON_AddOperator(smtk::model::OperatorPtr op, cJSON* opEntry)
    {
    cJSON_AddItemToObject(opEntry, "name", cJSON_CreateString(op->name().c_str()));
    smtk::attribute::AttributePtr spec = op->specification();
    if (spec)
      {
      cJSON_AddAttributeSpec(opEntry, "spec", "specXML", spec);
      }
    /*
    cJSON_AddItemToObject(opEntry, "parameters",
      cJSON_CreateParameterArray(op->parameters()));
      */
    return opEntry;
    }

  /*
  cJSON* cJSON_CreateOperatorArray(const smtk::model::Operators& ops)
    {
    cJSON* a = cJSON_CreateArray();
    for (smtk::model::Operators::const_iterator it = ops.begin(); it != ops.end(); ++it)
      {
      cJSON* opEntry = cJSON_CreateObject();
      cJSON_AddItemToArray(a, opEntry);
      cJSON_AddOperator(*it, opEntry);
      }
    return a;
    }
    */

  cJSON* cJSON_CreateUUIDArray(const smtk::common::UUID* uids, unsigned count)
    {
    cJSON* a = cJSON_CreateArray();
    for (unsigned i = 0; i < count; ++i)
      {
      cJSON_AddItemToArray(a, cJSON_CreateString(uids[i].toString().c_str()));
      }
    return a;
    }

  cJSON* cJSON_CreateStringArray(const std::string* strings, unsigned count)
    {
    cJSON* a = cJSON_CreateArray();
    for (unsigned i = 0; i < count; ++i)
      {
      cJSON_AddItemToArray(a, cJSON_CreateString(strings[i].c_str()));
      }
    return a;
    }
  cJSON* cJSON_CreateLongArray(const long* ints, unsigned count)
    {
    // FIXME: This uses a hack to speed up creation of arrays with many entries
    // cJSON should provide a method to append given last element in array and
    // its append method should return each newly-created element.

    cJSON* a = cJSON_CreateArray();
    cJSON** loc = &a->child;
    for (unsigned i = 0; i < count; ++i)
      {
      if (ints[i] > 9007199254740991.0) //== 2^53 - 1, max integer-accurate double
        {
        std::cerr << "Error exporting array: integer value " << i << " (" << ints[i] << ") out of range for cJSON\n";
        }
      *loc = cJSON_CreateNumber(ints[i]);
      loc = &((*loc)->next); // fast way to append to cJSON array
      }
    return a;
    }
}

namespace smtk {
  namespace io {

using smtk::common::UUID;

cJSON* ExportJSON::fromUUIDs(const UUIDs& uids)
{
  cJSON* a = cJSON_CreateArray();
  for (UUIDs::const_iterator it = uids.begin(); it != uids.end(); ++it)
    {
    cJSON_AddItemToArray(a, cJSON_CreateString(it->toString().c_str()));
    }
  return a;
}

int ExportJSON::fromModelManager(cJSON* json, ManagerPtr modelMgr, JSONFlags sections)
{
  int status = 0;
  if (!json || !modelMgr)
    {
    std::cerr << "Invalid arguments.\n";
    return status;
    }

  cJSON* body = cJSON_CreateObject();
  cJSON* sess = cJSON_CreateObject();
  cJSON* mesh = cJSON_CreateObject();
  switch(json->type)
    {
  case cJSON_Object:
    cJSON_AddItemToObject(json, "topo", body);
    cJSON_AddItemToObject(json, "sessions", sess);
    cJSON_AddItemToObject(json, "mesh_collections", mesh);
    break;
  case cJSON_Array:
    cJSON_AddItemToArray(json, body);
    cJSON_AddItemToArray(json, sess);
    break;
  case cJSON_NULL:
  case cJSON_Number:
  case cJSON_String:
  default:
    std::cerr << "Invalid toplevel JSON type (" << json->type << ").\n";
    return status;
    break;
    }

  cJSON* mtyp = cJSON_CreateString("Manager");
  cJSON_AddItemToObject(json, "type", mtyp);
  status = ExportJSON::forManager(body, sess, mesh, modelMgr, sections);

  return status;
}

std::string ExportJSON::fromModelManager(ManagerPtr modelMgr, JSONFlags sections)
{
  cJSON* top = cJSON_CreateObject();
  ExportJSON::fromModelManager(top, modelMgr, sections);
  char* json = cJSON_Print(top);
  std::string result(json);
  free(json);
  cJSON_Delete(top);
  return result;
}

bool ExportJSON::fromModelManagerToFile(smtk::model::ManagerPtr modelMgr, const char* filename)
{
  if (!filename || !modelMgr)
    return false;

  std::ofstream file(filename);
  file << ExportJSON::fromModelManager(modelMgr, JSON_DEFAULT);
  return true;
}

int ExportJSON::forManager(
  cJSON* dict, cJSON* sess, cJSON* mesh, ManagerPtr modelMgr, JSONFlags sections)
{
  if (!dict || !modelMgr)
    {
    return 0;
    }
  int status = 1;
  UUIDWithEntity it;

  if (sections == JSON_NOTHING)
    return status;

  for (it = modelMgr->topology().begin(); it != modelMgr->topology().end(); ++it)
    {
    if ((it->second.entityFlags() & SESSION) && !(sections & JSON_SESSIONS))
      continue;

    cJSON* curChild = cJSON_CreateObject();
      {
      std::string suid = it->first.toString();
      cJSON_AddItemToObject(dict, suid.c_str(), curChild);
      }
    if (sections & JSON_ENTITIES)
      {
      status &= ExportJSON::forManagerEntity(it, curChild, modelMgr);
      status &= ExportJSON::forManagerArrangement(
        modelMgr->arrangements().find(it->first), curChild, modelMgr);
      }
    if (sections & JSON_TESSELLATIONS)
      status &= ExportJSON::forManagerTessellation(it->first, curChild, modelMgr);
    if (sections & JSON_ANALYSISMESH)
      status &= ExportJSON::forManagerAnalysis(it->first, curChild, modelMgr);
    if (sections & JSON_PROPERTIES)
      {
      status &= ExportJSON::forManagerFloatProperties(it->first, curChild, modelMgr);
      status &= ExportJSON::forManagerStringProperties(it->first, curChild, modelMgr);
      status &= ExportJSON::forManagerIntegerProperties(it->first, curChild, modelMgr);
      }
    }

  if (sections & JSON_SESSIONS)
    {
    smtk::model::SessionRefs sessions = modelMgr->sessions();
    for (smtk::model::SessionRefs::iterator bit = sessions.begin(); bit != sessions.end(); ++bit)
      {
      status &= ExportJSON::forManagerSession(bit->entity(), sess, modelMgr);
      }
    }

  if (sections & JSON_MESHES)
    {
    smtk::mesh::ManagerPtr meshPtr = modelMgr->meshes();
    status &= ExportJSON::forManagerMeshes(meshPtr, mesh, modelMgr);
    }
  return status;
}

int ExportJSON::forManagerEntity(
  UUIDWithEntity& entry, cJSON* entRec, ManagerPtr model)
{
  (void)model;
  cJSON* ent = cJSON_CreateNumber(entry->second.entityFlags());
  cJSON* dim = cJSON_CreateNumber(entry->second.dimension());
  cJSON_AddItemToObject(entRec, "e", ent);
  cJSON_AddItemToObject(entRec, "d", dim);
  if (!entry->second.relations().empty())
    {
    cJSON_AddItemToObject(entRec, "r",
      cJSON_CreateUUIDArray(
        &entry->second.relations()[0],
        static_cast<unsigned int>(entry->second.relations().size())));
    }
  /*
  if (entry->second.entityFlags() & MODEL_ENTITY)
    ExportJSON::forModelOperators(entry->first, entRec, model);
    */
  return 1;
}

int ExportJSON::forManagerArrangement(
  const UUIDWithArrangementDictionary& entry, cJSON* dict, ManagerPtr model)
{
  if (entry == model->arrangements().end())
    {
    return 0;
    }
  ArrangementKindWithArrangements it;
  cJSON* arrNode = cJSON_CreateObject();
  cJSON_AddItemToObject(dict, "a", arrNode);
  for (it = entry->second.begin(); it != entry->second.end(); ++it)
    {
    Arrangements& arr(it->second);
    if (!arr.empty())
      {
      cJSON* kindNode = cJSON_CreateArray();
      cJSON_AddItemToObject(arrNode, smtk::model::AbbreviationForArrangementKind(it->first).c_str(), kindNode);
      Arrangements::iterator ait;
      for (ait = arr.begin(); ait != arr.end(); ++ait)
        {
        if (!ait->details().empty())
          {
          cJSON_AddItemToArray(kindNode,
            cJSON_CreateIntArray(&(ait->details()[0]), static_cast<int>(ait->details().size())));
          }
        }
      }
    }
  return 1;
}

int ExportJSON::forManagerTessellation(
  const smtk::common::UUID& uid, cJSON* dict, ManagerPtr model)
{
  UUIDWithTessellation tessIt = model->tessellations().find(uid);
  if (
    tessIt == model->tessellations().end() ||
    tessIt->second.coords().empty()
    )
    { // No tessellation? Not a problem.
    return 1;
    }
  //  "metadata": { "formatVersion" : 3 },
  //  "vertices": [ 0,0,0, 0,0,1, 1,0,1, 1,0,0, ... ],
  //  "normals":  [ 0,1,0, ... ],
  //  "faces": [
  //    0, 0,1,2, // tri
  //    1, 0,1,2,3, // quad
  //    32, 0,1,2, // tri w/ per-vert norm
  cJSON* tess = cJSON_CreateObject();
  //cJSON* meta = cJSON_CreateObject();
  cJSON* fmt = cJSON_CreateObject();
  cJSON_AddItemToObject(fmt,"formatVersion", cJSON_CreateNumber(3));
  //cJSON_AddItemToObject(meta, "metadata", fmt);
  //cJSON_AddItemToObject(tess, "3js", meta);
  cJSON_AddItemToObject(tess, "metadata", fmt);
  cJSON_AddItemToObject(tess, "vertices", cJSON_CreateDoubleArray(
      &tessIt->second.coords()[0],
      static_cast<int>(tessIt->second.coords().size())));
  cJSON_AddItemToObject(tess, "faces", cJSON_CreateIntArray(
      tessIt->second.conn().empty() ? NULL : &tessIt->second.conn()[0],
      static_cast<int>(tessIt->second.conn().size())));
  cJSON_AddItemToObject(dict, "t", tess);
  return 1;
}

int ExportJSON::forManagerAnalysis(
  const smtk::common::UUID& uid, cJSON* dict, ManagerPtr model)
{
  UUIDWithTessellation meshIt = model->analysisMesh().find(uid);
  if (
    meshIt == model->analysisMesh().end() ||
    meshIt->second.coords().empty()
    )
    { // No tessellation? Not a problem.
    return 1;
    }
  //  "metadata": { "formatVersion" : 3 },
  //  "vertices": [ 0,0,0, 0,0,1, 1,0,1, 1,0,0, ... ],
  //  "normals":  [ 0,1,0, ... ],
  //  "faces": [
  //    0, 0,1,2, // tri
  //    1, 0,1,2,3, // quad
  //    32, 0,1,2, // tri w/ per-vert norm
  cJSON* mesh = cJSON_CreateObject();
  //cJSON* meta = cJSON_CreateObject();
  cJSON* fmt = cJSON_CreateObject();
  cJSON_AddItemToObject(fmt,"formatVersion", cJSON_CreateNumber(3));
  //cJSON_AddItemToObject(meta, "metadata", fmt);
  //cJSON_AddItemToObject(tess, "3js", meta);
  cJSON_AddItemToObject(mesh, "metadata", fmt);
  cJSON_AddItemToObject(mesh, "vertices", cJSON_CreateDoubleArray(
      &meshIt->second.coords()[0],
      static_cast<int>(meshIt->second.coords().size())));
  cJSON_AddItemToObject(mesh, "faces", cJSON_CreateIntArray(
      meshIt->second.conn().empty() ? NULL : &meshIt->second.conn()[0],
      static_cast<int>(meshIt->second.conn().size())));
  cJSON_AddItemToObject(dict, "m", mesh);
  return 1;
}

int ExportJSON::forFloatData(cJSON* dict, const FloatData& fdata)
{
  cJSON* pdict = cJSON_CreateObject();
  cJSON_AddItemToObject(dict, "f", pdict);
  PropertyNameWithConstFloats entry;
  for (entry = fdata.begin(); entry != fdata.end(); ++entry)
    {
    if (entry->second.empty())
      {
      continue;
      }
    cJSON_AddItemToObject(pdict, entry->first.c_str(),
      cJSON_CreateDoubleArray(
        &entry->second[0], static_cast<int>(entry->second.size())));
    }
  return 1;
}

int ExportJSON::forStringData(cJSON* dict, const StringData& sdata)
{
  cJSON* pdict = cJSON_CreateObject();
  cJSON_AddItemToObject(dict, "s", pdict);
  PropertyNameWithConstStrings entry;
  for (entry = sdata.begin(); entry != sdata.end(); ++entry)
    {
    if (entry->second.empty())
      {
      continue;
      }
    cJSON_AddItemToObject(pdict, entry->first.c_str(),
      cJSON_CreateStringArray(
        &entry->second[0], static_cast<unsigned int>(entry->second.size())));
    }
  return 1;
}

int ExportJSON::forIntegerData(cJSON* dict, const IntegerData& idata)
{
  cJSON* pdict = cJSON_CreateObject();
  cJSON_AddItemToObject(dict, "i", pdict);
  PropertyNameWithConstIntegers entry;
  for (entry = idata.begin(); entry != idata.end(); ++entry)
    {
    if (entry->second.empty())
      {
      continue;
      }
    cJSON_AddItemToObject(pdict, entry->first.c_str(),
      cJSON_CreateLongArray(
        &entry->second[0], static_cast<unsigned int>(entry->second.size())));
    }
  return 1;
}

int ExportJSON::forManagerFloatProperties(const smtk::common::UUID& uid, cJSON* dict, ManagerPtr model)
{
  int status = 1;
  UUIDWithFloatProperties entIt = model->floatProperties().find(uid);
  if (entIt == model->floatProperties().end() || entIt->second.empty())
    { // No properties is not an error
    return status;
    }
  return ExportJSON::forFloatData(dict, entIt->second);
}

int ExportJSON::forManagerStringProperties(const smtk::common::UUID& uid, cJSON* dict, ManagerPtr model)
{
  int status = 1;
  UUIDWithStringProperties entIt = model->stringProperties().find(uid);
  if (entIt == model->stringProperties().end() || entIt->second.empty())
    { // No properties is not an error
    return status;
    }
  return ExportJSON::forStringData(dict, entIt->second);
}

int ExportJSON::forManagerIntegerProperties(const smtk::common::UUID& uid, cJSON* dict, ManagerPtr model)
{
  int status = 1;
  UUIDWithIntegerProperties entIt = model->integerProperties().find(uid);
  if (entIt == model->integerProperties().end() || entIt->second.empty())
    { // No properties is not an error
    return status;
    }
  return ExportJSON::forIntegerData(dict, entIt->second);
}

int ExportJSON::forManagerSession(
  const smtk::common::UUID& uid,
  cJSON* node,
  ManagerPtr modelMgr,
  bool writeNativeModels,
  const std::string& refPath)
{
  int status = 1;
  SessionPtr session = SessionRef(modelMgr, uid).session();
  if (!session)
    return status;

  cJSON* sess = cJSON_CreateObject();
  cJSON_AddItemToObject(node, uid.toString().c_str(), sess);
  cJSON_AddStringToObject(sess, "type", "session");
  cJSON_AddStringToObject(sess, "name", session->name().c_str());
  SessionIOJSONPtr delegate =
    smtk::dynamic_pointer_cast<SessionIOJSON>(
      session->createIODelegate("json"));
  if (delegate)
    {
    delegate->setReferencePath(refPath);
    status &= delegate->exportJSON(modelMgr, session, sess, writeNativeModels);
    }

  smtk::model::Models modelsOfSession = SessionRef(modelMgr, session).models<smtk::model::Models>();
  ExportJSON::addModelsRecord(modelMgr, modelsOfSession, sess);
  ExportJSON::addMeshesRecord(modelMgr, modelsOfSession, sess);

  status &= ExportJSON::forOperatorDefinitions(session->operatorSystem(), sess);
  return status;
}

int ExportJSON::forManagerSessionPartial(
  const smtk::common::UUID& sessionid,
  const smtk::common::UUIDs& modelIds,
  cJSON* node,
  ManagerPtr modelMgr,
  bool writeNativeModels,
  const std::string& refPath)
{
  int status = 1;
  SessionPtr session = SessionRef(modelMgr, sessionid).session();
  if (!session)
    return status;

  cJSON* sess = cJSON_CreateObject();
  cJSON_AddItemToObject(node, sessionid.toString().c_str(), sess);
  cJSON_AddStringToObject(sess, "type", "session");
  cJSON_AddStringToObject(sess, "name", session->name().c_str());

  SessionIOJSONPtr delegate =
    smtk::dynamic_pointer_cast<SessionIOJSON>(
      session->createIODelegate("json"));
  if (delegate)
    {
    delegate->setReferencePath(refPath);
    status &= delegate->exportJSON(modelMgr, session, modelIds, sess, writeNativeModels);
    }
  ExportJSON::addModelsRecord(modelMgr, modelIds, sess);
  ExportJSON::addMeshesRecord(modelMgr, modelIds, sess);
  status &= ExportJSON::forOperatorDefinitions(session->operatorSystem(), sess);
  return status;
}

/*
int ExportJSON::forModelOperators(const smtk::common::UUID& uid, cJSON* entRec, ManagerPtr modelMgr)
{
  smtk::model::Model mod(modelMgr, uid);
  smtk::model::Operators ops(mod.operators());
  cJSON_AddItemToObject(entRec, "ops",
    cJSON_CreateOperatorArray(ops));`
  return 1; // ExportJSON::forOperators(ops, entRec);
} */

int ExportJSON::forOperatorDefinitions(smtk::attribute::System* opSys, cJSON* entRec)
{
  smtk::io::Logger log;
  smtk::io::AttributeWriter wri;
  wri.includeDefinitions(true);
  wri.includeInstances(false);
  wri.includeModelInformation(false);
  wri.includeViews(true); // now operator could specify views
  std::string xml;
  bool err = wri.writeContents(*opSys, xml, log, true);
  if (!err)
    {
    cJSON_AddItemToObject(entRec, "ops",
      cJSON_CreateString(xml.c_str()));
    }
  /*
  std::vector<smtk::attribute::DefinitionPtr> ops;
  opSys.derivedDefinitions(
    opSys.findDefinition("operator"), ops);
  if (!ops.empty())
    {
    cJSON_AddItemToObject(entRec, "ops",
      cJSON_CreateOperatorArray(ops));
    }
    */
  return 1;
}

int ExportJSON::forOperator(OperatorPtr op, cJSON* entRec)
{
  cJSON_AddOperator(op, entRec);
  return 1;
}

int ExportJSON::forOperator(smtk::attribute::AttributePtr op, cJSON* entRec)
{
  cJSON_AddItemToObject(entRec, "name", cJSON_CreateString(op->type().c_str()));
  cJSON_AddAttributeSpec(entRec, "spec", "specXML", op);
  return 1;
}

int ExportJSON::forOperatorResult(OperatorResult res, cJSON* entRec)
{
  cJSON_AddItemToObject(entRec, "name", cJSON_CreateString(res->type().c_str()));
  cJSON_AddAttributeSpec(entRec, "result", "resultXML", res);
  EntityRefs ents = res->modelEntitiesAs<EntityRefs>("created");
  EntityRefs mdfs = res->modelEntitiesAs<EntityRefs>("modified");
  EntityRefs meshents = res->modelEntitiesAs<EntityRefs>("mesh_created");

  ents.insert(mdfs.begin(), mdfs.end());
  ents.insert(meshents.begin(), meshents.end());
  if (!ents.empty())
    {
    // If the operator reports new/modified entities, transcribe the affected models.
    // TODO: In the future, this may be more conservative (i.e., fewer records
    //       would be included to save time and memory) than ITERATE_MODELS.
    cJSON* records = cJSON_CreateObject();
    ExportJSON::forEntities(records, ents, smtk::model::ITERATE_MODELS, JSON_CLIENT_DATA);
    cJSON_AddItemToObject(entRec, "records", records);
    }

  smtk::attribute::MeshItemPtr modifiedMeshes = res->findMesh("mesh_modified");
  // Also export JSON meshes as a new node "mesh_records"
  if(!meshents.empty() || modifiedMeshes)
    {
    // get all collections associated with the input entities
    smtk::common::UUIDs collectionIds;
    smtk::mesh::ManagerPtr meshMgr = res->modelManager()->meshes();
    smtk::model::EntityRefs::const_iterator iter;
    for (iter = meshents.begin(); iter != meshents.end(); ++iter)
      {
      smtk::common::UUIDs cids = meshMgr->associatedCollectionIds(*iter);
      collectionIds.insert(cids.begin(), cids.end());
      }
    if(modifiedMeshes)
      {
      smtk::attribute::MeshItem::const_mesh_it it;
      for(it = modifiedMeshes->begin(); it != modifiedMeshes->end(); ++it)
        {
        collectionIds.insert(it->collection()->entity());
        }
      }
    if(collectionIds.size() > 0)
      {
      cJSON* mesh_records = cJSON_CreateObject();
      ExportJSON::forMeshCollections(mesh_records, collectionIds, meshMgr);
      cJSON_AddItemToObject(entRec, "mesh_records", mesh_records);
      }
    }

  return 1;
}

/// Serialize a session's list of dangling entities held in the given \a modelMgr.
int ExportJSON::forDanglingEntities(const smtk::common::UUID& sessionId, cJSON* node, ManagerPtr modelMgr)
{
  if (!modelMgr || !node || node->type != cJSON_Object)
    return 0;
  SessionPtr session = SessionRef(modelMgr, sessionId).session();
  if (!session)
    return 0;

  cJSON* danglers = cJSON_CreateObject();
  cJSON* darray = cJSON_CreateObject();
  cJSON_AddItemToObject(node, "danglingEntities", danglers);
  cJSON_AddItemToObject(danglers, "sessionId", cJSON_CreateString(sessionId.toString().c_str()));
  cJSON_AddItemToObject(danglers, "entities", darray);
  DanglingEntities::const_iterator it;
  for (it = session->danglingEntities().begin(); it != session->danglingEntities().end(); ++it)
    {
    if (it->first.manager() == modelMgr)
      cJSON_AddItemToObject(darray, it->first.entity().toString().c_str(), cJSON_CreateNumber(it->second));
    }
  return 1;
}

/**\brief Serialize a description of a Remus model worker.
  *
  * This populates an empty JSON Object (\a wdesc) with
  * data required for a Remus server to start an smtk-model-worker
  * process for use by a RemusRemoteSession instance.
  */
int ExportJSON::forModelWorker(
    cJSON* wdesc,
    const std::string& meshTypeIn, const std::string& meshTypeOut,
    smtk::model::SessionPtr session, const std::string& engine,
    const std::string& site, const std::string& root,
    const std::string& workerPath, const std::string& requirementsFileName)
{
  if (!wdesc || wdesc->type != cJSON_Object)
    return 0;

  // Base worker requirements
  cJSON_AddItemToObject(wdesc, "InputType", cJSON_CreateString(meshTypeIn.c_str()));
  cJSON_AddItemToObject(wdesc, "OutputType", cJSON_CreateString(meshTypeOut.c_str()));
  cJSON_AddItemToObject(wdesc, "ExecutableName", cJSON_CreateString(workerPath.c_str()));

  // External requirements information
  cJSON_AddItemToObject(wdesc, "File", cJSON_CreateString(requirementsFileName.c_str()));
  cJSON_AddItemToObject(wdesc, "FileFormat", cJSON_CreateString("XML"));

  // Additional requirements for SMTK model worker
  cJSON* argArray = cJSON_CreateArray();
  cJSON_AddItemToArray(argArray, cJSON_CreateString("-rwfile=@SELF@"));
  cJSON_AddItemToArray(argArray, cJSON_CreateString(("-kernel=" + session->name()).c_str()));
  if (!engine.empty())
    cJSON_AddItemToArray(argArray, cJSON_CreateString(("-engine=" + engine).c_str()));
  if (!site.empty())
    cJSON_AddItemToArray(argArray, cJSON_CreateString(("-site=" + site).c_str()));
  if (!root.empty())
    cJSON_AddItemToArray(argArray, cJSON_CreateString(("-root=" + root).c_str()));
  cJSON_AddItemToObject(wdesc, "Arguments", argArray);

  // TODO: Handle workers that can support multiple kernels as well as
  //       multiple engines.
  cJSON* tag = cJSON_Parse(SessionRegistrar::sessionTags(session->name()).c_str());
  if (!tag)
    return 0;

  cJSON_AddItemToObject(wdesc, "Tag", tag);
  cJSON_AddItemToObject(tag, "default_kernel", cJSON_CreateString(session->name().c_str()));
  cJSON_AddItemToObject(tag, "default_engine", cJSON_CreateString(engine.c_str()));
  cJSON_AddItemToObject(tag, "site", cJSON_CreateString(site.c_str()));
  cJSON_AddItemToObject(tag, "smtk_version",
    cJSON_CreateString(smtk::common::Version::number().c_str()));
  return 1;
}

/**\brief Serialize all the smtk::mesh associated with a given smtk::model.
  *
  * This populates an empty JSON Object (\a mdesc) with
  * data required to recreate the smtk::mesh Collections
  * associated with the given smtk::model.
  */
int ExportJSON::forManagerMeshes(
                     smtk::mesh::ManagerPtr meshes,
                     cJSON* mdesc,
                     smtk::model::ManagerPtr modelMgr)
{
  (void)modelMgr;
  //current issue is that a mesh Manager needs to know where to write
  //these collections to disk.

  if (!mdesc || mdesc->type != cJSON_Object)
    {
    return 0;
    }

  //step 0. get all collections in the manager
  int status = 1;
  std::vector< smtk::mesh::CollectionPtr > collections;
  typedef smtk::mesh::Manager::const_iterator cit;
  for (cit it = meshes->collectionBegin();
       it != meshes->collectionEnd(); ++it)
    {
    status &= forSingleCollection(mdesc, it->second);
    }

  return status;
}

/**\brief Serialize input mesh Collections.
  *
  * This creates and populate an JSON Object "mesh_collections"
  * and add it to the parent json node (\a pnode) with
  * data required to recreate the mesh Collections
  * associated with the given \a collectionIds of mesh manager (\a meshMgr)
  */
int ExportJSON::forMeshCollections(
                     cJSON* pnode,
                     const smtk::common::UUIDs& collectionIds,
                     smtk::mesh::ManagerPtr meshMgr)
{
  if (!pnode || pnode->type != cJSON_Object)
    {
    return 0;
    }

  if(collectionIds.empty())
    {
    return 0;
    }
  int status = 1;
  cJSON* mesh = cJSON_GetObjectItem(pnode, "mesh_collections");
  if(!mesh)
    {
    mesh = cJSON_CreateObject();
    cJSON_AddItemToObject(pnode, "mesh_collections", mesh);
    }

  smtk::common::UUIDs::const_iterator cit;
  for(cit = collectionIds.begin(); cit != collectionIds.end(); ++cit)
    {
    status &= forSingleCollection(mesh, meshMgr->collection(*cit));
    }

  return status;
}

/**\brief Serialize all the mesh collections associated with given \a modelid.
  *
  * This creates and populate an JSON Object "mesh_collections"
  * and add it to the parent json node (\a pnode) with
  * all mesh collections associated with the given \a modelid.
  */
int ExportJSON::forModelMeshes(
                     const smtk::common::UUID& modelid,
                     cJSON* pnode,
                     smtk::model::ManagerPtr modelMgr)
{
  if (!pnode || pnode->type != cJSON_Object)
    {
    return 0;
    }
  smtk::mesh::ManagerPtr meshMgr = modelMgr->meshes();
  smtk::model::Model model(modelMgr, modelid);
  if(!model.isValid() || !meshMgr)
    {
    return 0;
    }

  smtk::common::UUIDs cids = meshMgr->associatedCollectionIds(model);
  return ExportJSON::forMeshCollections(pnode, cids, meshMgr);
}

namespace {


//--------------------------------------------------------------------------
template< typename T>
void writeIntegerValues( cJSON* parent,
                        std::vector<T> const& values,
                        std::string name )
{
  cJSON* a = cJSON_CreateArray();
  for (std::size_t i = 0; i < values.size(); ++i)
    {
    cJSON_AddItemToArray(a, cJSON_CreateNumber(values[i].value()));
    }
  cJSON_AddItemToObject(parent, name.c_str(), a);
}

//--------------------------------------------------------------------------
void writeHandleValues( cJSON* parent,
                        smtk::mesh::HandleRange const& values,
                        std::string name )
{
  //need to implement a free method like:
  cJSON* json = smtk::mesh::to_json(values);
  cJSON_AddItemToObject(parent,
                        name.c_str(),
                        json);
}

//--------------------------------------------------------------------------
void writeBoundaryConditions( cJSON* parent, const smtk::mesh::MeshSet& mesh)
{
  cJSON* boundaryJson = cJSON_CreateObject();
  cJSON_AddItemToObject(parent, "boundary_conditions", boundaryJson);

  std::size_t index = 0;
  std::stringstream buffer;

  //list out the dirichlets that this mesheset contains
  std::vector< smtk::mesh::Dirichlet > dirichlets = mesh.dirichlets();
  for(std::size_t i=0; i < dirichlets.size(); ++i)
    {
    cJSON* conditionJson = cJSON_CreateObject();
    cJSON_AddItemToObject(conditionJson,"value", cJSON_CreateNumber(dirichlets[i].value()));
    cJSON_AddItemToObject(conditionJson,"type", cJSON_CreateString("dirichlet"));

    //convert the index to a string.
    buffer << index++;
    std::string sindex = buffer.str();
    cJSON_AddItemToObject(boundaryJson, sindex.c_str(), conditionJson);

    buffer.str("");
    }

  //list out the neumanns that this mesheset contains
  std::vector< smtk::mesh::Neumann > neumanns = mesh.neumanns();
  for(std::size_t i=0; i < neumanns.size(); ++i)
    {
    cJSON* conditionJson = cJSON_CreateObject();
    cJSON_AddItemToObject(conditionJson,"value", cJSON_CreateNumber(neumanns[i].value()));
    cJSON_AddItemToObject(conditionJson,"type", cJSON_CreateString("neumann"));

    //convert the index to a string.
    buffer << index++;
    std::string sindex = buffer.str();
    cJSON_AddItemToObject(boundaryJson, sindex.c_str(), conditionJson);
    }
}

//--------------------------------------------------------------------------
void writeUUIDValues( cJSON* parent,
                smtk::common::UUIDArray const& values,
                std::string name )
{
  cJSON_AddItemToObject(parent, name.c_str(),
                        cJSON_CreateUUIDArray(&values[0], values.size()) );
}

class ForMeshset : public smtk::mesh::MeshForEach
{
public:
  ForMeshset(cJSON* json):
    smtk::mesh::MeshForEach(),
    m_json(json),
    m_index(0)
  {
  }

  //--------------------------------------------------------------------------
  void write(const smtk::mesh::MeshSet& mesh, cJSON* parent,
             bool writeMeshes, bool writeCellAndPoints)
  {
    std::string cell_bit_types = mesh.types().cellTypes().to_string();

    cJSON_AddStringToObject(parent,"cell_types", cell_bit_types.c_str());

    //needs to be by value since cells() will go out out of scope, and
    //we don't want a reference to a stack object that has gone out of scope
    if(writeMeshes)
      {
      smtk::mesh::HandleRange meshes = mesh.range();
      //note we uses meshIds, since 'meshes' is used by the actual mesh dict
      writeHandleValues(parent, meshes, std::string("meshIds") );
      }

    if(writeCellAndPoints)
      {
      smtk::mesh::HandleRange cells = mesh.cells().range();
      writeHandleValues(parent, cells, std::string("cells") );

      smtk::mesh::HandleRange points = mesh.points().range();
      writeHandleValues(parent, points, std::string("points") );
      }

    //list out the domains that this mesheset contains
    std::vector< smtk::mesh::Domain > domains = mesh.domains();
    writeIntegerValues(parent, domains, std::string("domains") );

    //write out the boundary conditions of this meshset
    writeBoundaryConditions(parent, mesh);

    //list out the model associations that this mesheset contains
    smtk::common::UUIDArray modelEntityIds = mesh.modelEntityIds();
    writeUUIDValues(parent, modelEntityIds, std::string("modelEntityIds") );
  }

  //--------------------------------------------------------------------------
  void writeProperties(const smtk::mesh::CollectionPtr& collection,
                       cJSON* parent)
  {
    smtk::mesh::MeshFloatData* fProperties =
      collection->properties<smtk::mesh::MeshFloatData>();
    smtk::mesh::MeshStringData* sProperties =
      collection->properties<smtk::mesh::MeshStringData>();
    smtk::mesh::MeshIntegerData* iProperties =
      collection->properties<smtk::mesh::MeshIntegerData>();
    // if there are no properties, just return
    if( !(fProperties && fProperties->size() > 0) &&
        !(sProperties && sProperties->size() > 0) &&
        !(iProperties && iProperties->size() > 0))
      {
      return;
      }
    std::string meshid("meshid");
    cJSON* jsonProperties = cJSON_CreateObject();
    cJSON_AddItemToObject(parent, "properties", jsonProperties);
    if(fProperties && fProperties->size() > 0)
      {
      for(smtk::mesh::MeshFloatData::const_iterator it = fProperties->begin();
          it != fProperties->end(); ++it)
        {
        cJSON* jMesh = smtk::mesh::to_json(it->first.range());
        cJSON_AddItemToObject(jsonProperties, meshid.c_str(), jMesh);
        ExportJSON::forFloatData(jMesh, it->second);
        }
      }
    if(sProperties && sProperties->size() > 0)
      {
      for(smtk::mesh::MeshStringData::const_iterator it = sProperties->begin();
          it != sProperties->end(); ++it)
        {
        cJSON* jMesh = smtk::mesh::to_json(it->first.range());
        cJSON_AddItemToObject(jsonProperties, meshid.c_str(), jMesh);
        ExportJSON::forStringData(jMesh, it->second);
        }
      }
    if(iProperties && iProperties->size() > 0)
      {
      for(smtk::mesh::MeshIntegerData::const_iterator it = iProperties->begin();
          it != iProperties->end(); ++it)
        {
        cJSON* jMesh = smtk::mesh::to_json(it->first.range());
        cJSON_AddItemToObject(jsonProperties, meshid.c_str(), jMesh);
        ExportJSON::forIntegerData(jMesh, it->second);
        }
      }
  }

  //--------------------------------------------------------------------------
  void forMesh(smtk::mesh::MeshSet& mesh)
  {
    cJSON* meshJson = cJSON_CreateObject();

    const bool writeMeshes = false;
    const bool writeCellAndPoints = true;
    this->write(mesh, meshJson, writeMeshes, writeCellAndPoints);

    //convert the index to a string. assign it as the name of the meshset
    std::stringstream buffer;
    buffer << this->m_index++;
    std::string sindex = buffer.str();

    cJSON_AddItemToObject(this->m_json,
                          sindex.c_str(),
                          meshJson);
  }

private:
  cJSON* m_json;
  int m_index;
};

}
/**\brief Serialize a single mesh collection
  *
  */
int ExportJSON::forSingleCollection(cJSON* mdesc,
                                    smtk::mesh::CollectionPtr collection)
{
  cJSON* jsonCollection = cJSON_CreateObject();

  std::string collectionUUID = collection->entity().toString();
  cJSON_AddItemToObject(mdesc, collectionUUID.c_str(), jsonCollection);

  cJSON_AddItemToObject(jsonCollection,"formatVersion", cJSON_CreateNumber(1));

  cJSON_AddStringToObject(jsonCollection,"name", collection->name().c_str());
  //assoicated model uuid of the collection
  if(!collection->associatedModel().isNull())
    {
    cJSON_AddStringToObject(jsonCollection,"associatedModel",
                            collection->associatedModel().toString().c_str());
    }

  std::string interfaceName = collection->interfaceName();
  cJSON_AddStringToObject(jsonCollection, "type", interfaceName.c_str());

  bool writeMeshFileOk = true;
  //now actually write out the collection to disk, and add the location keyword
  //if we have a file.
  //This allows the importer to use the location keyword to figure out
  //if it should use the type, or fall back to the json interface
  if(!collection->writeLocation().empty())
    {
    // if there is a reference path, write out the relative path to it;
    // otherwise, write out the absolute path
    const std::string& fileWriteLocation =
      collection->writeLocation().referencePath().empty() ?
      collection->writeLocation().absolutePath() :
      collection->writeLocation().relativePath();

    cJSON_AddStringToObject(jsonCollection, "location", fileWriteLocation.c_str() );
    writeMeshFileOk = smtk::io::writeMesh(collection, mesh::Subset::EntireCollection);
    }

  //currently we only write out the modification state after the possibility
  //of a file write. This is done so that meshes that have a file location
  //have a modified state of false
  bool isModified = collection->isModified();
  cJSON_AddBoolToObject(jsonCollection, "modified", isModified);

  ///now to dump everything inside the collection by reusing the class
  //that writes out a single meshset, but instead pass it all meshsets
  ForMeshset addInfoAboutCollection(jsonCollection);
  const bool writeMeshes = true;
  const bool writeCellAndPoints = false;
  addInfoAboutCollection.write(collection->meshes(), jsonCollection,
                               writeMeshes, writeCellAndPoints);

  ///wirte out mesh properties in the collection.
  addInfoAboutCollection.writeProperties(collection, jsonCollection);

  //now walk through each meshset and dump  all the info related to it.
  cJSON* jsonMeshes = cJSON_CreateObject();
  cJSON_AddItemToObject(jsonCollection, "meshes", jsonMeshes);

  smtk::mesh::MeshSet meshes = collection->meshes();
  ForMeshset perMeshExportToJson(jsonMeshes);
  smtk::mesh::for_each(meshes, perMeshExportToJson);

  return writeMeshFileOk ? 1 : 0;
}

/**\brief Add records for \a modelIds to its parent \a sessionRec.
  *
  * This will add a "models" record to \a sessionRec, and all models
  * will be added as children of "models"
  */
int ExportJSON::addModelsRecord(
  const smtk::model::ManagerPtr modelMgr,
  const smtk::common::UUIDs& modelIds,
  cJSON* sessionRec)
{
  smtk::model::Models models;
  smtk::model::EntityRef::EntityRefsFromUUIDs(models, modelMgr, modelIds);
  return ExportJSON::addModelsRecord(modelMgr, models, sessionRec);
}

int ExportJSON::addModelsRecord(
  const smtk::model::ManagerPtr modelMgr,
  const smtk::model::Models& inModels,
  cJSON* sessionRec)
{
  // This static method's signature matches the other "add###Record" methods,
  // but parameter <modelMgr> is unused. To remove "unused parameter" warnings,
  // we therefore cast <modelMgr> to void rather than remove the ManagerPtr from
  // the signature.
  (void)modelMgr;

  cJSON* jmodels = cJSON_CreateObject();
  cJSON_AddItemToObject(sessionRec, "models", jmodels);

  // add record for each model
  smtk::model::Models::const_iterator modit;
  for (modit = inModels.begin(); modit != inModels.end(); ++modit)
    {
    //smtk::model::Model model(modelMgr, *modit);
    cJSON* jmodel = cJSON_CreateObject();

    // Write out all entities of the model, only the meta data
    smtk::model::Models currentmodels;
    currentmodels.push_back(*modit);
    ExportJSON::forEntities(
      jmodel, currentmodels,
      smtk::model::ITERATE_MODELS,
      static_cast<smtk::io::JSONFlags>(
        smtk::io::JSON_ENTITIES | smtk::io::JSON_PROPERTIES));

    cJSON_AddItemToObject(jmodels, modit->entity().toString().c_str(), jmodel);
    }
  return 1;
}

/**\brief Add records for meshes of \a modelIds to its parent \a sessionRec.
  *
  * This will add a "mesh_collections" record to \a sessionRec, and all meshes
  * will be added as children of "mesh_collections"
  */
int ExportJSON::addMeshesRecord(
  const smtk::model::ManagerPtr modelMgr,
  const smtk::common::UUIDs& modelIds,
  cJSON* sessionRec)
{
  smtk::model::Models models;
  smtk::model::EntityRef::EntityRefsFromUUIDs(models, modelMgr, modelIds);
  return ExportJSON::addMeshesRecord(modelMgr, models, sessionRec);
}

int ExportJSON::addMeshesRecord(
  const smtk::model::ManagerPtr modelMgr,
  const smtk::model::Models& inModels,
  cJSON* sessionRec)
{
  // Add record for each model
  smtk::model::Models::const_iterator modit;
  for (modit = inModels.begin(); modit != inModels.end(); ++modit)
    {
    // Write out related mesh collections.
    // When writing a single collection, all its MeshSets will also be written out.
    smtk::io::ExportJSON::forModelMeshes(modit->entity(), sessionRec, modelMgr);
    }
  return 1;
}

/**\brief Export log records into a cJSON array.
  *
  * You must pass in a valid cJSON array (\a logrecordarray).
  * Log entries will be appended to the end of it (and will
  * not overwrite any pre-existing array entries).
  *
  * If you specify \a start and \a end, then only the specified
  * subset of records from \a log will be added to
  * \a logrecordarray.
  * As with C++ iterators, \a start and \a end describe a
  * half-open interval.
  */
int ExportJSON::forLog(
  cJSON* logrecordarray,
  const smtk::io::Logger& log,
  std::size_t start,
  std::size_t end)
{
  if (
    !logrecordarray ||
    logrecordarray->type != cJSON_Array)
    return -1;

  // Figure out where to stop writing entries:
  int numberOfRecords = 0;
  std::size_t finish = log.numberOfRecords();
  if (end < finish)
    finish = end;
  // Advance to the end of the array.
  cJSON* lastEntry = logrecordarray->child;
  while (lastEntry && lastEntry->next)
    lastEntry = lastEntry->next;
  cJSON* first = NULL;
  for (; start < finish; ++start)
    {
    const smtk::io::Logger::Record& rec(log.record(start));
    if (rec.message.empty())
      continue;

    cJSON* entry = cJSON_CreateArray();
    ++numberOfRecords;

    cJSON_AddItemToArray(entry,
      cJSON_CreateNumber(
        static_cast<int>(rec.severity)));
    cJSON_AddItemToArray(entry,
      cJSON_CreateString(
        rec.message.c_str()));
    if (!rec.fileName.empty())
      cJSON_AddItemToArray(entry,
        cJSON_CreateString(
          rec.fileName.c_str()));
    if (rec.lineNumber)
      cJSON_AddItemToArray(entry,
        cJSON_CreateNumber(
          rec.lineNumber));

    // Append cJSON entry to lastEntry:
    if (lastEntry)
      lastEntry->next = entry;
    else
      first = entry;
    entry->prev = lastEntry;
    lastEntry = entry;
    }
  if (!logrecordarray->child && first)
    logrecordarray->child = first;
  return numberOfRecords;
}

/**\brief Create a JSON-RPC request object.
  *
  * This variant stores a valid pointer in \a params for you to populate.
  *
  * Note that \a paramsType can be used to control the type of structure
  * created to hold parameters. JSON-RPC v2.0 allows for either an Array
  * or an Object structure (while v1 required an Array).
  * The value should be either cJSON_Object or cJSON_Array.
  * If unspecified, cJSON_Array is used.
  * Although not part of the JSON-RPC spec, True, False, and NULL parameters
  * are also accepted by this call.
  */
cJSON* ExportJSON::createRPCRequest(const std::string& method, cJSON*& params, const std::string& reqId, int paramsType)
{
  cJSON* rpcReq = cJSON_CreateObject();
  cJSON_AddItemToObject(rpcReq, "jsonrpc", cJSON_CreateString("2.0"));
  cJSON_AddItemToObject(rpcReq, "method", cJSON_CreateString(method.c_str()));
  cJSON_AddItemToObject(rpcReq, "id", cJSON_CreateString(reqId.c_str()));
  switch (paramsType)
    {
  case cJSON_Array:
    params = cJSON_CreateArray();
    break;
  case cJSON_Object:
    params = cJSON_CreateObject();
    break;
  case cJSON_True:
    params = cJSON_CreateTrue();
    break;
  case cJSON_False:
    params = cJSON_CreateFalse();
    break;
  case cJSON_NULL:
    params = cJSON_CreateNull();
    break;
  default:
    // TODO: Should we emit an error here?
    return rpcReq;
    break;
    }
  cJSON_AddItemToObject(rpcReq, "params", params);
  return rpcReq;
}

/**\brief Create a JSON-RPC request object.
  *
  * This variant stores a single string parameter, \a params.
  */
cJSON* ExportJSON::createRPCRequest(
  const std::string& method,
  const std::string& params,
  const std::string& reqId)
{
  cJSON* paramObj;
  cJSON* rpcReq = ExportJSON::createRPCRequest(method, paramObj, reqId, cJSON_Array);
  cJSON_AddItemToArray(paramObj, cJSON_CreateString(params.c_str()));
  return rpcReq;
}

/**\brief Copy \a arr into a JSON array of strings.
  *
  * You are responsible for managing the memory allocated for the returned
  * object, either by calling cJSON_Delete on it or adding it to another
  * cJSON node that is eventually deleted.
  */
cJSON* ExportJSON::createStringArray(const std::vector<std::string>& arr)
{
  return cJSON_CreateStringArray(&arr[0], static_cast<unsigned>(arr.size()));
}

/**\brief Copy \a arr into a JSON array of UUIDs.
  *
  * You are responsible for managing the memory allocated for the returned
  * object, either by calling cJSON_Delete on it or adding it to another
  * cJSON node that is eventually deleted.
  */
cJSON* ExportJSON::createUUIDArray(const std::vector<smtk::common::UUID>& arr)
{
  return cJSON_CreateUUIDArray(&arr[0], static_cast<unsigned>(arr.size()));
}

/**\brief Copy \a arr into a JSON array of integers.
  *
  * You are responsible for managing the memory allocated for the returned
  * object, either by calling cJSON_Delete on it or adding it to another
  * cJSON node that is eventually deleted.
  */
cJSON* ExportJSON::createIntegerArray(const std::vector<long>& arr)
{
  return cJSON_CreateLongArray(&arr[0], static_cast<unsigned>(arr.size()));
}

  }
}
