/*
 * Performs a set of semantic consistency checks on the document.  Query
 * the results by calling getNumErrors() and getError().
 *
 * @return the number of failed checks (errors) encountered.
 */
unsigned int
SBMLInternalValidator::checkConsistency (bool writeDocument)
{
  unsigned int nerrors = 0;
  unsigned int total_errors = 0;

  //if (getLevel() == 3)
  //{
  //  logError(L3NotSupported);
  //  return 1;
  //}
  /* determine which validators to run */
  bool id    = ((mApplicableValidators & 0x01) == 0x01);
  bool sbml  = ((mApplicableValidators & 0x02) == 0x02);
  bool sbo   = ((mApplicableValidators & 0x04) == 0x04);
  bool math  = ((mApplicableValidators & 0x08) == 0x08);
  bool units = ((mApplicableValidators & 0x10) == 0x10);
  bool over  = ((mApplicableValidators & 0x20) == 0x20);
  bool practice = ((mApplicableValidators & 0x40) == 0x40);

  /* taken the state machine concept out for now
  if (LibSBMLStateMachine::isActive()) 
  {
    units = LibSBMLStateMachine::getUnitState();
  }
  */

  IdentifierConsistencyValidator id_validator;
  ConsistencyValidator validator;
  SBOConsistencyValidator sbo_validator;
  MathMLConsistencyValidator math_validator;
  UnitConsistencyValidator unit_validator;
  OverdeterminedValidator over_validator;
  ModelingPracticeValidator practice_validator;

  SBMLDocument *doc;
  SBMLErrorLog *log = getErrorLog();
  
  if (writeDocument)
  {
    char* sbmlString = writeSBMLToString(getDocument());
    log->clearLog();
    doc = readSBMLFromString(sbmlString);
    free (sbmlString);  
  }
  else
  {
    doc = getDocument();
  }

  /* calls each specified validator in turn 
   * - stopping when errors are encountered */

  if (id)
  {
    id_validator.init();
    nerrors = id_validator.validate(*doc);
    if (nerrors > 0) 
    {
      unsigned int origNum = log->getNumErrors();
      log->add( id_validator.getFailures() );

      if (origNum > 0 && log->contains(InvalidUnitIdSyntax) == true)
      {
        /* do not log dangling ref */
        while (log->contains(DanglingUnitSIdRef) == true)
        {
          log->remove(DanglingUnitSIdRef);
          nerrors--;
        }
        
        total_errors += nerrors;
        if (nerrors > 0)
        {
          if (writeDocument)
            SBMLDocument_free(doc);
          return total_errors;
        }
      }
      else if (log->contains(DanglingUnitSIdRef) == false)
      {
        total_errors += nerrors;
        if (writeDocument)
          SBMLDocument_free(doc);
        return total_errors;
      }
      else
      {
        bool onlyDangRef = true;
        for (unsigned int a = 0; a < log->getNumErrors(); a++)
        {
          if (log->getError(a)->getErrorId() != DanglingUnitSIdRef)
          {
            onlyDangRef = false;
            break;
          }
        }
        total_errors += nerrors;

        if (onlyDangRef == false)
        {
          if (writeDocument)
            SBMLDocument_free(doc);
          return total_errors;
        }
      }
    }
  }

  if (sbml)
  {
    validator.init();
    nerrors = validator.validate(*doc);
    total_errors += nerrors;
    if (nerrors > 0) 
    {
      log->add( validator.getFailures() );
      /* only want to bail if errors not warnings */
      if (log->getNumFailsWithSeverity(LIBSBML_SEV_ERROR) > 0)
      {
        if (writeDocument)
          SBMLDocument_free(doc);
        return total_errors;
      }
    }
  }

  if (sbo)
  {
    sbo_validator.init();
    nerrors = sbo_validator.validate(*doc);
    total_errors += nerrors;
    if (nerrors > 0) 
    {
      log->add( sbo_validator.getFailures() );
      /* only want to bail if errors not warnings */
      if (log->getNumFailsWithSeverity(LIBSBML_SEV_ERROR) > 0)
      {
        if (writeDocument)
          SBMLDocument_free(doc);
        return total_errors;
      }
    }
  }

  if (math)
  {
    math_validator.init();
    nerrors = math_validator.validate(*doc);
    total_errors += nerrors;
    if (nerrors > 0) 
    {
      log->add( math_validator.getFailures() );
      /* at this point bail if any problems
       * unit checks may crash if there have been math errors/warnings
       */
      if (writeDocument)
        SBMLDocument_free(doc);
      return total_errors;
    }
  }


  if (units)
  {
    unit_validator.init();
    nerrors = unit_validator.validate(*doc);
    total_errors += nerrors;
    if (nerrors > 0) 
    {
      log->add( unit_validator.getFailures() );
      /* only want to bail if errors not warnings */
      if (log->getNumFailsWithSeverity(LIBSBML_SEV_ERROR) > 0)
      {
        if (writeDocument)
          SBMLDocument_free(doc);
        return total_errors;
      }
    }
  }

  /* do not even try if there have been unit warnings 
   * changed this as would have bailed */
  if (over)
  {
    over_validator.init();
    nerrors = over_validator.validate(*doc);
    total_errors += nerrors;
    if (nerrors > 0) 
    {
      log->add( over_validator.getFailures() );
      /* only want to bail if errors not warnings */
      if (log->getNumFailsWithSeverity(LIBSBML_SEV_ERROR) > 0)
      {
        if (writeDocument)
          SBMLDocument_free(doc);
        return total_errors;
      }
    }
  }

  if (practice)
  {
    practice_validator.init();
    nerrors = practice_validator.validate(*doc);
    if (nerrors > 0) 
    {
      unsigned int errorsAdded = 0;
      const std::list<SBMLError> practiceErrors = practice_validator.getFailures();
      list<SBMLError>::const_iterator end = practiceErrors.end();
      list<SBMLError>::const_iterator iter;
      for (iter = practiceErrors.begin(); iter != end; ++iter)
      {
        if (SBMLError(*iter).getErrorId() != 80701)
        {
          log->add( SBMLError(*iter) );
          errorsAdded++;
        }
        else
        {
          if (units) 
          {
            log->add( SBMLError(*iter) );
            errorsAdded++;
          }
        }
      }
      total_errors += errorsAdded;

    }
  }

  if (writeDocument)
    SBMLDocument_free(doc);
  return total_errors;
}
/** @cond doxygenLibsbmlInternal */
int
CompFlatteningConverter::validateFlatDocument(Model * flatmodel,
            unsigned int pkgVersion, unsigned int level, unsigned int version)
{
  int result;
  // create a dummyDocument that will mirror what the user options are 
   // we need the dummyDoc to know things about unknown packages
  // but dont want the original model/error log
  SBMLDocument dummy = SBMLDocument(*(mDocument));
  dummy.setModel(NULL);
  dummy.getErrorLog()->clearLog();
   
  // Otherwise, transfer only errors 1090107->1090110
  SBMLErrorLog* log = mDocument->getErrorLog();
  for (unsigned int en=0; en<log->getNumErrors(); en++) 
  {
    unsigned int errid = mDocument->getError(en)->getErrorId();
    if (errid == CompFlatteningNotRecognisedNotReqd ||
        errid == CompFlatteningNotRecognisedReqd ||
        errid == CompFlatteningNotImplementedNotReqd ||
        errid == CompFlatteningNotImplementedReqd)
    {
          dummy.getErrorLog()->add(*(mDocument->getError(en)));
    }
  }

  log->clearLog();

  result = reconstructDocument(flatmodel, dummy, true );
  if (result != LIBSBML_OPERATION_SUCCESS)
  {
    //delete flatmodel;
    restoreNamespaces();
    return result;
  }

 //Now check to see if the flat model is valid
  // run regular validation on the flattened document if requested.

  // override comp flattening if necessary
  CompSBMLDocumentPlugin * dummyPlugin = static_cast<CompSBMLDocumentPlugin*>
                                         (dummy.getPlugin("comp"));

  if (dummyPlugin != NULL)
  {
    dummyPlugin->setOverrideCompFlattening(true);
  }

  std::string sbml = writeSBMLToStdString(&dummy);
  SBMLDocument *tempdoc = readSBMLFromString(sbml.c_str());
  unsigned int errors = 
           tempdoc->getErrorLog()->getNumFailsWithSeverity(LIBSBML_SEV_ERROR);
  
  // take out the error about a requiredpackage
  // if the user has specified to not abort for any packages
  // NOTE: we cannot actually remove it since the flattening code
  // uses it to check whether references might come from
  // other packages
  if (getAbortForNone() == true)
  {
    if (tempdoc->getErrorLog()->contains(RequiredPackagePresent))
    {
      errors--;
    }
  }

  if (errors > 0)
  {
    // we have serious errors so we are going to bail on the
    // flattening - log ONLY the errors
    //Transfer the errors to mDocument and don't reset the model.
    if (log->contains(CompLineNumbersUnreliable) == false)
    {
      log->logPackageError("comp", CompLineNumbersUnreliable, 
        pkgVersion, level, version);
    }
    std::string message = "Errors that follow relate to the flattened ";
    message += "document produced using the CompFlatteningConverter.";
    log->logPackageError("comp", CompFlatModelNotValid,
        pkgVersion, level, version);
  
    unsigned int nerrors = tempdoc->getErrorLog()->getNumErrors();
    for (unsigned int n = 0; n < nerrors; n++)
    {
      const SBMLError* error = tempdoc->getError(n);
      if (error->getSeverity() >= LIBSBML_SEV_ERROR) 
      {
        log->add( *(error) );
      }
      if (error->getErrorId() >= CompFlatteningNotRecognisedNotReqd &&
        error->getErrorId() <= CompFlatteningNotImplementedReqd) 
      {
        log->add( *(error) );
      }
      else if (error->getErrorId() == UnrequiredPackagePresent ||
        error->getErrorId() == RequiredPackagePresent)
      {
        log->add( *(error) );
      }
    }
    //delete flatmodel;
    restoreNamespaces();
    delete tempdoc;
    return LIBSBML_CONV_INVALID_SRC_DOCUMENT;
  }
  else
  {
    delete tempdoc;
  }

  dummy.checkConsistency();

  if (dummyPlugin != NULL)
  {
    dummyPlugin->setOverrideCompFlattening(false);
  }

  errors = dummy.getErrorLog()->getNumFailsWithSeverity(LIBSBML_SEV_ERROR);
  if (errors > 0)
  {
    // we have serious errors so we are going to bail on the
    // flattening - log ONLY the errors
    //Transfer the errors to mDocument and don't reset the model.
    if (log->contains(CompLineNumbersUnreliable) == false)
    {
      log->logPackageError("comp", CompLineNumbersUnreliable, 
        pkgVersion, level, version);
    }
    std::string message = "Errors that follow relate to the flattened ";
    message += "document produced using the CompFlatteningConverter.";
    log->logPackageError("comp", CompFlatModelNotValid,
        pkgVersion, level, version);
  
    unsigned int nerrors = dummy.getErrorLog()->getNumErrors();
    for (unsigned int n = 0; n < nerrors; n++)
    {
      const SBMLError* error = dummy.getError(n);
      if (error->getSeverity() >= LIBSBML_SEV_ERROR) 
      {
        log->add( *(error) );
      }
      if (error->getErrorId() >= CompFlatteningNotRecognisedNotReqd &&
        error->getErrorId() <= CompFlatteningNotImplementedReqd) 
      {
        log->add( *(error) );
      }
      else if (error->getErrorId() == UnrequiredPackagePresent ||
        error->getErrorId() == RequiredPackagePresent)
      {
        log->add( *(error) );
      }
    }
    //delete flatmodel;
    restoreNamespaces();
    return LIBSBML_CONV_INVALID_SRC_DOCUMENT;
  }
  else
  {
    // put any warnings into the document that will be have the
    // flat model
    unsigned int nerrors = dummy.getErrorLog()->getNumErrors();
    for (unsigned int n = 0; n < nerrors; n++)
    {
      const SBMLError* error = dummy.getError(n);
      log->add( *(error) );
    }

    return LIBSBML_OPERATION_SUCCESS;
  }
}