END_TEST


START_TEST (test_comp_externalmodelresolving_files)
{ 
  // load the document
  string fileuri(TestDataDirectory);
  fileuri = fileuri + "subdir/localExtMod.xml";
  SBMLDocument *document = readSBMLFromFile(fileuri.c_str());
  CompSBMLDocumentPlugin* compdoc = static_cast<CompSBMLDocumentPlugin*>(document->getPlugin("comp"));

  ExternalModelDefinition* extdef = compdoc->getExternalModelDefinition(0);
  fail_unless(extdef != NULL);
  Model* resolvedmod = extdef->getReferencedModel();
  fail_unless(resolvedmod != NULL);

  delete document;
 }
END_TEST

START_TEST (test_comp_sbmldocument)
{
  SBMLNamespaces sbmlns(3,1,"comp",1);
  SBMLDocument doc(&sbmlns);
  CompSBMLDocumentPlugin* docplugin = static_cast<CompSBMLDocumentPlugin*>(doc.getPlugin("comp"));
  doc.setPackageRequired("comp", true);
  fail_unless(docplugin->getRequired() == true);

  fail_unless(docplugin->getNumModelDefinitions()==0);
  ModelDefinition* moddef = docplugin->createModelDefinition();
  moddef->setId("moddef1");
  fail_unless(docplugin->getNumModelDefinitions()==1);
  fail_unless(docplugin->addModelDefinition(NULL)==LIBSBML_INVALID_OBJECT);
  ModelDefinition moddef2(3, 1);
  moddef2.setId("moddef2");
  fail_unless(docplugin->addModelDefinition(&moddef2)==LIBSBML_OPERATION_SUCCESS);
  ModelDefinition* moddefref = docplugin->getModelDefinition("moddef2");
  fail_unless(moddefref->getId()=="moddef2");
  moddefref = docplugin->getModelDefinition(0);
  fail_unless(moddefref->getId()=="moddef1");
  moddef->setId("ID1");
  fail_unless(moddefref->getId()=="ID1");
  fail_unless(docplugin->removeModelDefinition(3)==NULL);
  fail_unless(docplugin->removeModelDefinition(0)==moddef);
  fail_unless(docplugin->getModelDefinition("moddef1")==NULL);

  fail_unless(docplugin->getNumExternalModelDefinitions()==0);
  ExternalModelDefinition* exmoddef = docplugin->createExternalModelDefinition();
  exmoddef->setId("exmoddef1");
  fail_unless(docplugin->getNumExternalModelDefinitions()==1);
  fail_unless(docplugin->addExternalModelDefinition(NULL)==LIBSBML_INVALID_OBJECT);
  ExternalModelDefinition exmoddef2(3, 1);
  exmoddef2.setId("exmoddef2");
  fail_unless(docplugin->addExternalModelDefinition(&exmoddef2)==LIBSBML_INVALID_OBJECT);
  exmoddef2.setSource("where/the/file/is.xml");
  fail_unless(docplugin->addExternalModelDefinition(&exmoddef2)==LIBSBML_OPERATION_SUCCESS);
  ExternalModelDefinition* exmoddefref = docplugin->getExternalModelDefinition("exmoddef2");
  fail_unless(exmoddefref->getId()=="exmoddef2");
  exmoddefref = docplugin->getExternalModelDefinition(0);
  fail_unless(exmoddefref->getId()=="exmoddef1");
  exmoddef->setModelRef("ID1");
  fail_unless(exmoddefref->getModelRef()=="ID1");
  fail_unless(docplugin->removeExternalModelDefinition(3)==NULL);
  fail_unless(docplugin->removeExternalModelDefinition(0)==exmoddef);
  fail_unless(docplugin->getExternalModelDefinition("exmoddef1")==NULL);

}
int 
Submodel::instantiate()
{
  SBMLDocument* doc = getSBMLDocument();
  SBMLDocument* rootdoc = doc;
  if (doc==NULL) 
  {
    return LIBSBML_OPERATION_FAILED;
  }

  CompSBMLDocumentPlugin* docplugin = 
    static_cast<CompSBMLDocumentPlugin*>(doc->getPlugin(getPrefix()));
  if (docplugin==NULL)
  {
    return LIBSBML_OPERATION_FAILED;
  }

  SBase* parent  = getParentSBMLObject();
  string parentmodelname = "";
  string parentURI = "";
  set<string> uniqueModels;
  while (parent != NULL && parent->getTypeCode() != SBML_DOCUMENT) {
    if (parent->getTypeCode() == SBML_COMP_SUBMODEL) {
      const Submodel* parentsub = static_cast<const Submodel*>(parent);
      uniqueModels.insert(parentsub->mInstantiationOriginalURI + "::" + parentsub->getModelRef());
      if (parentURI=="") {
        parentURI=parentsub->mInstantiationOriginalURI;
      }
    }
    if (parent->getTypeCode() == SBML_MODEL ||
      parent->getTypeCode() == SBML_COMP_MODELDEFINITION)
    {
      if (parentmodelname == "") {
        parentmodelname = parent->getId();
      }
    }
    rootdoc = parent->getSBMLDocument();
    parent = parent->getParentSBMLObject();
  }

  if (mInstantiatedModel != NULL) 
  {
    delete mInstantiatedModel;
    mInstantiatedModel = NULL;
    mInstantiationOriginalURI.clear();
  }

  if (!hasRequiredAttributes()) {
    string error = "Instantiation error in Submodel::instantiate:  ";
    if (!isSetId()) {
      error += "A submodel in model '" + getParentModel(this)->getId() + "' does not have an 'id' attribute.";
    }
    else if (!isSetModelRef()) {
      error += "The submodel '" + getId() + "' does not have a 'modelRef' attribute.";
    }
    rootdoc->getErrorLog()->logPackageError("comp", CompSubmodelAllowedAttributes, getPackageVersion(), getLevel(), getVersion(), error, getLine(), getColumn());
    return LIBSBML_INVALID_OBJECT;
  }

  SBase* origmodel = docplugin->getModel(getModelRef());
  
  if (origmodel==NULL) {
    string error = "In Submodel::instantiate, unable to instantiate submodel '" + getId() + "' because the referenced model ('" + getModelRef() +"') does not exist.";
    rootdoc->getErrorLog()->logPackageError("comp", CompSubmodelMustReferenceModel, getPackageVersion(), getLevel(), getVersion(), error, getLine(), getColumn());
    return LIBSBML_INVALID_OBJECT;
  }
  ExternalModelDefinition* extmod;
  SBMLDocument* origdoc = NULL;
  string newmodel = parentURI + "::" + getModelRef();
  
  set<pair<string, string> > parents;
  switch(origmodel->getTypeCode()) 
  {
  case SBML_MODEL:
  case SBML_COMP_MODELDEFINITION:
    origdoc = origmodel->getSBMLDocument();
    mInstantiatedModel = static_cast<Model*>(origmodel)->clone();
    if (uniqueModels.insert(newmodel).second == false) {
      //Can't instantiate this model, because we are already a child of it.
      string error = "Error in Submodel::instantiate:  cannot instantiate submodel '" + getId() + "' in model '" + parentmodelname + "' because it references the model '" + getModelRef() + "', which is already an ancestor of the submodel.";
      rootdoc->getErrorLog()->logPackageError("comp", CompSubmodelCannotReferenceSelf, getPackageVersion(), getLevel(), getVersion(), error, getLine(), getColumn());
      return LIBSBML_OPERATION_FAILED;
    }
    mInstantiationOriginalURI = parentURI;
    break;
  case SBML_COMP_EXTERNALMODELDEFINITION:
    extmod = static_cast<ExternalModelDefinition*>(origmodel);
    if (extmod==NULL) 
    {
      //No error message:  it should be impossible, if origmodel has the type code 'external model definition', for it to not be castable to an external model definition.
      mInstantiatedModel = NULL;
      mInstantiationOriginalURI = "";
      return LIBSBML_OPERATION_FAILED;
    }
    mInstantiatedModel = extmod->getReferencedModel(rootdoc, parents);
    if (mInstantiatedModel == NULL) 
    {
      string error = "In Submodel::instantiate, unable to instantiate submodel '" + getId() + "' because the external model definition it referenced (model '" + getModelRef() +"') could not be resolved.";
      rootdoc->getErrorLog()->logPackageError("comp", CompSubmodelMustReferenceModel, getPackageVersion(), getLevel(), getVersion(), error, getLine(), getColumn());
      mInstantiationOriginalURI = "";
      return LIBSBML_OPERATION_FAILED;
    }
    mInstantiationOriginalURI = extmod->getSource();
    origdoc = mInstantiatedModel->getSBMLDocument();
    newmodel = extmod->getSource() + "::" + getModelRef();
    if (uniqueModels.insert(newmodel).second == false) {
      //Can't instantiate this model, because we are already a child of it.
      string error = "Error in Submodel::instantiate:  cannot instantiate submodel '" + getId() + "' in model '" + parentmodelname + "' because it references the model '" + getModelRef() + "', which is already an ancestor of the submodel.";
      rootdoc->getErrorLog()->logPackageError("comp", CompSubmodelCannotReferenceSelf, getPackageVersion(), getLevel(), getVersion(), error, getLine(), getColumn());
      mInstantiatedModel = NULL;
      mInstantiationOriginalURI = "";
      return LIBSBML_OPERATION_FAILED;
    }
    mInstantiatedModel = mInstantiatedModel->clone();
    mInstantiationOriginalURI = extmod->getSource();
    break;
  default:
    //Should always be one of the above, unless someone extends one of the above and doesn't tell us.
    string error = "Instantiation error in Submodel::instantiate:  unable to parse the model '" + origmodel->getId() + "', as it was not of the type 'model' 'modelDefinition', or 'externalModelDefinition'.  The most likely cause of this situation is if some other package extended one of those three types, but the submodel code was not updated.";
    rootdoc->getErrorLog()->logPackageError("comp", CompUnresolvedReference, getPackageVersion(), getLevel(), getVersion(), error, getLine(), getColumn());
    mInstantiatedModel = NULL;
    mInstantiationOriginalURI = "";
    return LIBSBML_OPERATION_FAILED;
  }
  
  if (mInstantiatedModel==NULL) 
  {
    string error = "Instantiation error in Submodel::instantiate:  unable to create a valid copy of model '" + getModelRef() + "'.";
    rootdoc->getErrorLog()->logPackageError("comp", CompModelFlatteningFailed, getPackageVersion(), getLevel(), getVersion(), error, getLine(), getColumn());
    mInstantiationOriginalURI = "";
    return LIBSBML_OPERATION_FAILED;
  }

  mInstantiatedModel->connectToParent(this);
  mInstantiatedModel->setSBMLDocument(origdoc);
  mInstantiatedModel->enablePackage(getPackageURI(), getPrefix(), true);
  CompModelPlugin* instmodplug = 
    static_cast<CompModelPlugin*>(mInstantiatedModel->getPlugin(getPrefix()));
  if (instmodplug == NULL)
  {
    mInstantiatedModel->enablePackageInternal(getPackageURI(), getPrefix(), true);
  }

  // call all registered callbacks
  std::vector<ModelProcessingCallbackData*>::iterator it = mProcessingCBs.begin();
  while(it != mProcessingCBs.end())
  {
    ModelProcessingCallbackData* current = *it;
    int result = current->cb(mInstantiatedModel, rootdoc->getErrorLog(), current->data);
    if (result != LIBSBML_OPERATION_SUCCESS)
      return result;
    ++it;
  }

  
  CompModelPlugin* origmodplug = 
    static_cast<CompModelPlugin*>(rootdoc->getModel()->getPlugin(getPrefix()));

  instmodplug = 
    static_cast<CompModelPlugin*>(mInstantiatedModel->getPlugin(getPrefix()));
  
  if (instmodplug == NULL)
    return LIBSBML_OPERATION_SUCCESS;

  // if we have a transformer specified, then we need to propagate it, so it can
  // be used
  if (origmodplug->isSetTransformer())
  {
    if (instmodplug != NULL)
      instmodplug->setTransformer(origmodplug->getTransformer());
  }

  
  for (unsigned int sub=0; sub<instmodplug->getNumSubmodels(); sub++) 
  {
    Submodel* instsub = instmodplug->getSubmodel(sub);
    int ret = instsub->instantiate();
    if (ret != LIBSBML_OPERATION_SUCCESS) {
      //'instantiate' already sets its own error messages.
      delete mInstantiatedModel;
      mInstantiatedModel = NULL;
      mInstantiationOriginalURI = "";
      return ret;
    }
  }

  return LIBSBML_OPERATION_SUCCESS;
}
int main(int argc,char** argv)
{
  int retval = 0;
  SBMLNamespaces sbmlns(3,1,"comp",1);

  // create the document
  SBMLDocument *document = new SBMLDocument(&sbmlns);

  //Define the external model definition
  CompSBMLDocumentPlugin* compdoc
      = static_cast<CompSBMLDocumentPlugin*>(document->getPlugin("comp"));
  compdoc->setRequired(true);
  ExternalModelDefinition* extmod = compdoc->createExternalModelDefinition();
  extmod->setId("ExtMod1");
  extmod->setSource("enzyme_model.xml");
  extmod->setModelRef("enzyme");

  //Define the 'simple' model
  ModelDefinition* mod1 = compdoc->createModelDefinition();
  mod1->setId("simple");
  Compartment* comp=mod1->createCompartment();
  comp->setSpatialDimensions((unsigned int)3);
  comp->setConstant(true);
  comp->setId("comp");
  comp->setSize(1L);

  // We have to construct it this way because we get the comp
  // plugin from it later.
  Species spec(&sbmlns);
  spec.setCompartment("comp");
  spec.setHasOnlySubstanceUnits(false);
  spec.setConstant(false);
  spec.setBoundaryCondition(false);
  spec.setId("S");
  spec.setInitialConcentration(5);
  mod1->addSpecies(&spec);
  spec.setId("D");
  spec.setInitialConcentration(10);
  mod1->addSpecies(&spec);

  Reaction rxn(&sbmlns);
  rxn.setReversible(true);
  rxn.setFast(false);
  rxn.setId("J0");

  SpeciesReference sr(&sbmlns);
  sr.setConstant(true);
  sr.setStoichiometry(1);
  sr.setSpecies("S");
  rxn.addReactant(&sr);
  sr.setSpecies("D");
  rxn.addProduct(&sr);

  mod1->addReaction(&rxn);

  CompModelPlugin* mod1plug
      = static_cast<CompModelPlugin*>(mod1->getPlugin("comp"));
  Port port;
  port.setId("S_port");
  port.setIdRef("S");
  mod1plug->addPort(&port);

  Port* port2 = mod1plug->createPort();
  port2->setId("D_port");
  port2->setIdRef("D");

  port.setId("comp_port");
  port.setIdRef("comp");
  mod1plug->addPort(&port);

  port.setId("J0_port");
  port.setIdRef("J0");
  mod1plug->addPort(&port);

  // create the Model
  Model* model=document->createModel();
  model->setId("complexified");
  
  // Set the submodels
  CompModelPlugin* mplugin
      = static_cast<CompModelPlugin*>(model->getPlugin("comp"));
  Submodel* submod1 = mplugin->createSubmodel();
  submod1->setId("A");
  submod1->setModelRef("ExtMod1");
  Submodel* submod2 = mplugin->createSubmodel();
  submod2->setId("B");
  submod2->setModelRef("simple");
  Deletion* del = submod2->createDeletion();
  del->setPortRef("J0_port");

  // Synchronize the compartments
  Compartment* mcomp=model->createCompartment();
  mcomp->setSpatialDimensions((unsigned int)3);
  mcomp->setConstant(true);
  mcomp->setId("comp");
  mcomp->setSize(1L);
  CompSBasePlugin* compartplug
      = static_cast<CompSBasePlugin*>(mcomp->getPlugin("comp"));
  ReplacedElement re;
  re.setIdRef("comp");
  re.setSubmodelRef("A");
  compartplug->addReplacedElement(&re);
  re.setSubmodelRef("B");
  re.unsetIdRef();
  re.setPortRef("comp_port");
  compartplug->addReplacedElement(&re);

  //Synchronize the species
  spec.setId("S");
  spec.unsetInitialConcentration();
  CompSBasePlugin* specplug
      = static_cast<CompSBasePlugin*>(spec.getPlugin("comp"));
  ReplacedElement* sre = specplug->createReplacedElement();
  sre->setSubmodelRef("A");
  sre->setIdRef("S");
  ReplacedBy* srb = specplug->createReplacedBy();
  srb->setSubmodelRef("B");
  srb->setPortRef("S_port");
  model->addSpecies(&spec);

  spec.setId("D");
  sre->setIdRef("D");
  srb->setPortRef("D_port");
  model->addSpecies(&spec);

  writeSBMLToFile(document,"eg-ports.xml");
  writeSBMLToFile(document,"spec_example3.xml");
  delete document;
  document = readSBMLFromFile("spec_example3.xml");
  if (document == NULL)
  {
    cout << "Error reading back in file." << endl;
    retval = -1;
  }
  else
  {
    document->setConsistencyChecks(LIBSBML_CAT_UNITS_CONSISTENCY, false);
    document->checkConsistency();
    if (document->getErrorLog()->getNumFailsWithSeverity(2) > 0
        || document->getErrorLog()->getNumFailsWithSeverity(3) > 0)
    {
      stringstream errorstream;
      document->printErrors(errorstream);
      cout << "Errors encoutered when round-tripping  SBML file: \n"
           <<  errorstream.str() << endl;
      retval = -1;
    }
    writeSBMLToFile(document, "spec_example3_rt.xml");
    delete document;
  }
#ifdef WIN32
  if (retval != 0)
  {
    cout << "(Press any key to exit.)" << endl;
    _getch();
  }
#endif
  return retval;
}
int main(int argc,char** argv)
{
  int retval = 0;
  SBMLNamespaces sbmlns(3,1,"comp",1);

  // create the document
  SBMLDocument *document = new SBMLDocument(&sbmlns);

  //Define the external model definitions
  CompSBMLDocumentPlugin* compdoc
      = static_cast<CompSBMLDocumentPlugin*>(document->getPlugin("comp"));
  compdoc->setRequired(true);
  ExternalModelDefinition* extmod = compdoc->createExternalModelDefinition();
  extmod->setId("ExtMod1");
  extmod->setSource("enzyme_model.xml");
  extmod->setModelRef("enzyme");


  // create the main Model
  Model* model=document->createModel();
  
  // Set the submodels
  CompModelPlugin* mplugin = static_cast<CompModelPlugin*>(model->getPlugin("comp"));
  Submodel* submod1 = mplugin->createSubmodel();
  submod1->setId("A");
  submod1->setModelRef("ExtMod1");
  Submodel* submod2 = mplugin->createSubmodel();
  submod2->setId("B");
  submod2->setModelRef("ExtMod1");

  // create a replacement compartment
  Compartment* comp=model->createCompartment();
  comp->setSpatialDimensions((unsigned int)3);
  comp->setConstant(true);
  comp->setId("comp");
  comp->setSize(1L);

  //Tell the model that this compartment replaces both of the inside ones.
  CompSBasePlugin* compartplug = static_cast<CompSBasePlugin*>(comp->getPlugin("comp"));
  ReplacedElement re;
  re.setIdRef("comp");
  re.setSubmodelRef("A");
  compartplug->addReplacedElement(&re);
  re.setSubmodelRef("B");
  compartplug->addReplacedElement(&re);

  // create a replacement species
  Species* spec = model->createSpecies();
  spec->setCompartment("comp");
  spec->setHasOnlySubstanceUnits(false);
  spec->setConstant(false);
  spec->setBoundaryCondition(false);
  spec->setId("S");

  //Tell the model that this species replaces both of the inside ones.
  CompSBasePlugin* spp = static_cast<CompSBasePlugin*>(spec->getPlugin("comp"));
  re.setIdRef("S");
  re.setSubmodelRef("A");
  spp->addReplacedElement(&re);
  re.setSubmodelRef("B");
  spp->addReplacedElement(&re);

  writeSBMLToFile(document,"eg-import-external.xml");
  writeSBMLToFile(document,"spec_example2.xml");
  delete document;
  document = readSBMLFromFile("spec_example2.xml");
  if (document == NULL)
  {
    cout << "Error reading back in file." << endl;
    retval = -1;
  }
  else
  {
    document->setConsistencyChecks(LIBSBML_CAT_UNITS_CONSISTENCY, false);
    document->checkConsistency();
    if (document->getErrorLog()->getNumFailsWithSeverity(2) > 0
        || document->getErrorLog()->getNumFailsWithSeverity(3) > 0)
    {
      stringstream errorstream;
      document->printErrors(errorstream);
      cout << "Errors encoutered when round-tripping  SBML file: \n"
           <<  errorstream.str() << endl;
      retval = -1;
    }
    writeSBMLToFile(document, "spec_example2_rt.xml");
    delete document;
  }
#ifdef WIN32
  if (retval != 0)
  {
    cout << "(Press any key to exit.)" << endl;
    _getch();
  }
#endif
  return retval;
}
void
CompIdBase::checkId (const ExternalModelDefinition& x)
{
  if (x.isSetId()) doCheckId(x.getId(), x);
}