TEST_F(IdfFixture,WorkspaceObject_Construction) {
  // get vector of IdfFile objects as fodder for tests
  IdfObjectVector idfObjects = epIdfFile.objects();
  OptionalWorkspaceObject w;
  OptionalHandleVector hv;

  // construct empty workspace, and set Idd to be EnergyPlusIdd
  StrictnessLevel level(StrictnessLevel::Draft);
  IddFileType fileType(IddFileType::EnergyPlus);
  Workspace ws(level,fileType);

  // add object with no pointers. should be successful.
  w = ws.addObject(idfObjects[0]); // Building
  ASSERT_TRUE(w); // should return OptionalHandle instead
  OptionalWorkspaceObject object = ws.getObject(w->handle());
  ASSERT_TRUE(object);
  EXPECT_TRUE(object->iddObject().type() == openstudio::IddObjectType::Building);
  
  // add object with pointer, and do not include pointed to object. should be successful, but
  // pointer should be null. object.canBeSource() should be true.

  // add object with pointer, and do include pointed to object. should be successful, and 
  // following pointer should yield target object. source should be source, target should be
  // target.

}
TEST_F(IdfFixture, WorkspaceObject_Lights_Strictness_Draft) {

  Workspace workspace(StrictnessLevel::Draft,IddFileType::EnergyPlus);
  EXPECT_TRUE(workspace.isValid());

  OptionalWorkspaceObject w = workspace.addObject(IdfObject(IddObjectType::Lights));
  ASSERT_TRUE(w);

  OptionalWorkspaceObject light = workspace.getObject(w->handle());
  ASSERT_TRUE(light);

  // certain things we can't invalidate
  EXPECT_TRUE(light->setString(LightsFields::Name, ""));
  EXPECT_TRUE(light->setDouble(LightsFields::Name, 0));

  EXPECT_TRUE(light->setString(LightsFields::ZoneorZoneListName, "")); // PointerType error
  EXPECT_TRUE(light->setPointer(LightsFields::ZoneorZoneListName, Handle())); // PointerType error

  EXPECT_TRUE(light->setString(LightsFields::ScheduleName, "")); // PointerType error
  EXPECT_TRUE(light->setPointer(LightsFields::ScheduleName, Handle())); // PointerType error

  EXPECT_TRUE(light->setString(LightsFields::DesignLevelCalculationMethod, "")); // this is ok because there is a default
  EXPECT_FALSE(light->setString(LightsFields::DesignLevelCalculationMethod, "Hi")); // DataType error
  EXPECT_FALSE(light->setDouble(LightsFields::DesignLevelCalculationMethod, 0)); // DataType error

  EXPECT_FALSE(light->setString(LightsFields::LightingLevel, "Hi")); // DataType error
  EXPECT_FALSE(light->setDouble(LightsFields::LightingLevel, -1));  // NumericBound error
  EXPECT_TRUE(light->setDouble(LightsFields::LightingLevel, 0));
  EXPECT_TRUE(light->setDouble(LightsFields::LightingLevel, 1));

  EXPECT_TRUE(workspace.isValid(StrictnessLevel::None));
  EXPECT_TRUE(workspace.isValid(StrictnessLevel::Draft));
  EXPECT_FALSE(workspace.isValid(StrictnessLevel::Final));
}
TEST_F(IdfFixture, WorkspaceObject_Lights_Strictness_None) {

  Workspace workspace(StrictnessLevel::None,IddFileType::EnergyPlus);
  EXPECT_TRUE(workspace.isValid());

  OptionalWorkspaceObject w = workspace.addObject(IdfObject(IddObjectType::Lights));
  ASSERT_TRUE(w);

  OptionalWorkspaceObject light = workspace.getObject(w->handle());
  ASSERT_TRUE(light);

  // can invalidate all we want
  EXPECT_TRUE(light->setString(LightsFields::Name, ""));
  EXPECT_TRUE(light->setDouble(LightsFields::Name, 0));

  EXPECT_TRUE(light->setString(LightsFields::ZoneorZoneListName, ""));
  EXPECT_TRUE(light->setPointer(LightsFields::ZoneorZoneListName, Handle()));

  EXPECT_TRUE(light->setString(LightsFields::ScheduleName, ""));
  EXPECT_TRUE(light->setPointer(LightsFields::ScheduleName, Handle()));

  EXPECT_TRUE(light->setString(LightsFields::DesignLevelCalculationMethod, ""));
  EXPECT_TRUE(light->setDouble(LightsFields::DesignLevelCalculationMethod, 0));

  EXPECT_TRUE(light->setString(LightsFields::LightingLevel, "Hi"));
  EXPECT_TRUE(light->setDouble(LightsFields::LightingLevel, -1));
  EXPECT_TRUE(light->setDouble(LightsFields::LightingLevel, 0));
  EXPECT_TRUE(light->setDouble(LightsFields::LightingLevel, 1));

  EXPECT_TRUE(workspace.isValid(StrictnessLevel::None));
  EXPECT_FALSE(workspace.isValid(StrictnessLevel::Draft));
  EXPECT_FALSE(workspace.isValid(StrictnessLevel::Final));
}
TEST_F(IdfFixture,WorkspaceObjectWatcher_RelationshipFieldChanges_TargetDeleted)
{
  IdfObject object(IddObjectType::Lights);
  Workspace workspace(StrictnessLevel::Draft, IddFileType::EnergyPlus);

  OptionalWorkspaceObject owo = workspace.addObject(object);
  ASSERT_TRUE(owo);
  WorkspaceObject workspaceObject = *owo;
  WorkspaceObjectWatcher watcher(workspaceObject);
  EXPECT_FALSE(watcher.dirty());

  OptionalWorkspaceObject oZone = workspace.addObject(IdfObject(IddObjectType::Zone));
  ASSERT_TRUE(oZone);

  EXPECT_TRUE(workspaceObject.setPointer(LightsFields::ZoneorZoneListName, oZone->handle()));
  EXPECT_TRUE(watcher.dirty());
  EXPECT_FALSE(watcher.dataChanged());
  EXPECT_FALSE(watcher.nameChanged());
  EXPECT_TRUE(watcher.relationshipChanged());

  watcher.clearState();
  EXPECT_FALSE(watcher.dirty());
  EXPECT_FALSE(watcher.dataChanged());
  EXPECT_FALSE(watcher.nameChanged());
  EXPECT_FALSE(watcher.relationshipChanged());

  oZone->remove();
  EXPECT_TRUE(watcher.dirty());
  EXPECT_FALSE(watcher.dataChanged());
  EXPECT_FALSE(watcher.nameChanged());
  EXPECT_TRUE(watcher.relationshipChanged());
}
TEST_F(IdfFixture, WorkspaceObject_Filter_Sources)
{
  Workspace ws;
  OptionalWorkspaceObject node = ws.addObject(IdfObject(IddObjectType::OS_Node));
  OptionalWorkspaceObject node2 = ws.addObject(IdfObject(IddObjectType::OS_Node));
  OptionalWorkspaceObject node3 = ws.addObject(IdfObject(IddObjectType::OS_Node));
  OptionalWorkspaceObject spm = ws.addObject(IdfObject(IddObjectType::OS_SetpointManager_MixedAir));

  EXPECT_TRUE(spm->setPointer(OS_SetpointManager_MixedAirFields::SetpointNodeorNodeListName, node->handle()));
  EXPECT_TRUE(spm->setPointer(OS_SetpointManager_MixedAirFields::FanInletNodeName,node->handle()));
  EXPECT_TRUE(spm->setPointer(OS_SetpointManager_MixedAirFields::FanOutletNodeName,node2->handle()));
  EXPECT_TRUE(spm->setPointer(OS_SetpointManager_MixedAirFields::ReferenceSetpointNodeName,node3->handle()));

  WorkspaceObjectVector sourcesVector = node->sources();
  EXPECT_EQ(1, sourcesVector.size());
  sourcesVector = node->getSources(IddObjectType::OS_SetpointManager_MixedAir);
  EXPECT_EQ(1, sourcesVector.size());
}
TEST_F(EnergyPlusFixture,ReverseTranslator_Zone)
{
  Workspace inWorkspace(StrictnessLevel::None, IddFileType::EnergyPlus);
  OptionalWorkspaceObject zoneObject = inWorkspace.addObject(IdfObject(IddObjectType::Zone));
  ASSERT_TRUE(zoneObject);
  OptionalWorkspaceObject lightsObject = inWorkspace.addObject(IdfObject(IddObjectType::Lights));
  ASSERT_TRUE(lightsObject);
  EXPECT_TRUE(lightsObject->setPointer(openstudio::LightsFields::ZoneorZoneListName, zoneObject->handle()));

  ReverseTranslator reverseTranslator;
  ASSERT_NO_THROW(reverseTranslator.translateWorkspace(inWorkspace));
  Model model = reverseTranslator.translateWorkspace(inWorkspace);

  ASSERT_EQ(static_cast<unsigned>(1), model.getModelObjects<openstudio::model::ThermalZone>().size());
  openstudio::model::ThermalZone zone = model.getModelObjects<openstudio::model::ThermalZone>()[0];
  ASSERT_EQ(static_cast<unsigned>(1), model.getModelObjects<openstudio::model::Space>().size());
  openstudio::model::Space space = model.getModelObjects<openstudio::model::Space>()[0];
  ASSERT_EQ(static_cast<unsigned>(1), model.getModelObjects<openstudio::model::Lights>().size());
  openstudio::model::Lights lights = model.getModelObjects<openstudio::model::Lights>()[0];
  ASSERT_TRUE(lights.space());
  EXPECT_TRUE(space.handle() == lights.space()->handle());
  ASSERT_TRUE(lights.space()->thermalZone());
  EXPECT_TRUE(zone.handle() == lights.space()->thermalZone()->handle());
}
OptionalModelObject ReverseTranslator::translateFenestrationSurfaceDetailed( const WorkspaceObject & workspaceObject )
{
  if( workspaceObject.iddObject().type() != IddObjectType::FenestrationSurface_Detailed ){
    LOG(Error, "WorkspaceObject is not IddObjectType: Site:FenestrationSurface_Detailed");
    return boost::none;
  }

  openstudio::Point3dVector vertices = getVertices(FenestrationSurface_DetailedFields::NumberofVertices + 1, workspaceObject);
 
  boost::optional<SubSurface> subSurface;
  try{
    subSurface = SubSurface(vertices, m_model);
  }catch(const std::exception&){
    LOG(Error, "Cannot create SubSurface for object: " << workspaceObject);
    return boost::none;
  }

  OptionalString s = workspaceObject.name();
  if(s) {
    subSurface->setName(*s);
  }

  OptionalWorkspaceObject target = workspaceObject.getTarget(openstudio::FenestrationSurface_DetailedFields::ConstructionName);
  if (target){
    OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target);
    if (modelObject){
      if (modelObject->optionalCast<ConstructionBase>()){
        subSurface->setConstruction(modelObject->cast<ConstructionBase>());
      }
    }
  }

  target = workspaceObject.getTarget(openstudio::FenestrationSurface_DetailedFields::BuildingSurfaceName);
  if (target){
    OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target);
    if (modelObject){
      if (modelObject->optionalCast<Surface>()){
        subSurface->setSurface(modelObject->cast<Surface>());
      }
    }
  }

  // needs to be after .setSurface.
  s = workspaceObject.getString(FenestrationSurface_DetailedFields::SurfaceType);
  if (s) {
    if (istringEqual("Window", *s)){
      s = "FixedWindow";

      boost::optional<Surface> surface = subSurface->surface();
      if (surface){
        if ((surface->surfaceType() == "RoofCeiling") &&
            (surface->outsideBoundaryCondition() == "Outdoors")){
              s = "Skylight";
        }
      }
    }
    subSurface->setSubSurfaceType(*s);
  }

  target = workspaceObject.getTarget(openstudio::FenestrationSurface_DetailedFields::OutsideBoundaryConditionObject);
  if (target){
    if (target->iddObject().type() == IddObjectType::Zone){
      // Zone boundary condition

      OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target);
      if(modelObject->optionalCast<Space>()){
        Space adjacentSpace = modelObject->cast<Space>();

        OptionalSurface surface = subSurface->surface();
        if (surface && surface->space()){
          Space space = surface->space().get();

          if (surface->adjacentSurface()){
            Surface adjacentSurface = surface->adjacentSurface().get();

            if (adjacentSurface.space() && adjacentSpace.handle() == adjacentSurface.space()->handle()){
              Transformation transformation = adjacentSpace.transformation().inverse()*surface->space()->transformation();

              // duplicate subsurface in other space
              SubSurface adjacentSubSurface = subSurface->clone(m_model).cast<SubSurface>();
              adjacentSubSurface.setName(subSurface->name().get() + " Reversed");
              std::reverse(vertices.begin(), vertices.end());
              adjacentSubSurface.setVertices(transformation*vertices);
              adjacentSubSurface.setSurface(adjacentSurface);
              subSurface->setAdjacentSubSurface(adjacentSubSurface);

              return subSurface.get();
            }
          }
        }
      }

    }else if (target->iddObject().type() == IddObjectType::FenestrationSurface_Detailed){
      // SubSurface boundary condition

      // see if we have already mapped other sub surface, don't do it here because that is circular
      auto it = m_workspaceToModelMap.find(target->handle());
      if( it !=  m_workspaceToModelMap.end()){
        if (it->second.optionalCast<SubSurface>()){
          // this will set other side boundary object on both surfaces
          SubSurface adjacentSubSurface = it->second.cast<SubSurface>();
          subSurface->setAdjacentSubSurface(adjacentSubSurface);
          return subSurface.get();
        }
      }
    }else{  
      LOG(Error, "OutsideBoundaryConditionObject not yet mapped for object of type " << target->iddObject().name());
    }
  }

  // DLM: should these be before control paths that return above?
  OptionalDouble d = workspaceObject.getDouble(FenestrationSurface_DetailedFields::ViewFactortoGround);
  if (d) {
    subSurface->setViewFactortoGround(*d);
  }

  target = workspaceObject.getTarget(openstudio::FenestrationSurface_DetailedFields::ShadingControlName);
  if (target){
    LOG(Warn, "Shading Control Name not yet mapped for FenestrationSurface:Detailed");
  }

  target = workspaceObject.getTarget(openstudio::FenestrationSurface_DetailedFields::FrameandDividerName);
  if (target){
    LOG(Warn, "Frame and Divider Name not yet mapped for FenestrationSurface:Detailed");
  }

  OptionalInt i = workspaceObject.getInt(FenestrationSurface_DetailedFields::Multiplier);
  if (i) {
    subSurface->setMultiplier(*i);
  }

  return subSurface.get();
}
OptionalModelObject ReverseTranslator::translateBuildingSurfaceDetailed( const WorkspaceObject & workspaceObject )
{
 if( workspaceObject.iddObject().type() != IddObjectType::BuildingSurface_Detailed ){
   LOG(Error, "WorkspaceObject is not IddObjectType: BuildingSurface:Detailed");
    return boost::none;
  }

  openstudio::Point3dVector vertices = getVertices(BuildingSurface_DetailedFields::NumberofVertices + 1, workspaceObject);
 
  boost::optional<Surface> surface;
  try{
    surface = Surface(vertices, m_model);
  }catch(const std::exception&){
    LOG(Error, "Cannot create Surface for object: " << workspaceObject);
    return boost::none;
  }

  OptionalString s = workspaceObject.name();
  if(s) {
    surface->setName(*s);
  }

  OptionalWorkspaceObject target = workspaceObject.getTarget(openstudio::BuildingSurface_DetailedFields::ConstructionName);
  if (target){
    OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target);
    if (modelObject){
      if (modelObject->optionalCast<ConstructionBase>()){
        surface->setConstruction(modelObject->cast<ConstructionBase>());
      }
    }
  }

  target = workspaceObject.getTarget(openstudio::BuildingSurface_DetailedFields::ZoneName);
  if (target){
    OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target);
    if (modelObject){
      if (modelObject->optionalCast<Space>()){
        surface->setSpace(modelObject->cast<Space>());
      }
    }
  }

  s = workspaceObject.getString(BuildingSurface_DetailedFields::SurfaceType);
  if (s) {
    if (istringEqual("Roof", *s) || istringEqual("Ceiling", *s)){
      s = "RoofCeiling";
    }
    surface->setSurfaceType(*s);
  }
  //std::string surfaceType = surface->surfaceType();

  s = workspaceObject.getString(BuildingSurface_DetailedFields::SunExposure);
  if (s) {
    surface->setSunExposure(*s);
  }

  s = workspaceObject.getString(BuildingSurface_DetailedFields::WindExposure);
  if (s) {
    surface->setWindExposure(*s);
  }

  OptionalDouble d = workspaceObject.getDouble(BuildingSurface_DetailedFields::ViewFactortoGround);
  if (d) {
    surface->setViewFactortoGround(*d);
  }

  target = workspaceObject.getTarget(openstudio::BuildingSurface_DetailedFields::OutsideBoundaryConditionObject);
  if (target){

    if (target->iddObject().type() == IddObjectType::Zone){
      // Zone boundary condition

      OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target);
      if(modelObject->optionalCast<Space>()){
        Space adjacentSpace = modelObject->cast<Space>();

        if (surface->space()){
          // insert this surface in the map so subsurface translation can find it  
          m_workspaceToModelMap.insert(std::make_pair(workspaceObject.handle(), surface.get()));

          // need to translate all sub surfaces here so they will be in adjacent space
          for (const WorkspaceObject& workspaceSubSurface : workspaceObject.getSources(IddObjectType::FenestrationSurface_Detailed)){
            translateAndMapWorkspaceObject(workspaceSubSurface);
          }

          // create adjacent surface in other space
          surface->createAdjacentSurface(adjacentSpace);
          return surface.get();
        }
      }

    }else if (target->iddObject().type() == IddObjectType::BuildingSurface_Detailed){
      // Surface boundary condition

      // see if we have already mapped other surface, don't do it here because that is circular
      if (target->handle() == workspaceObject.handle() ){
        // these objects are the same, set boundary condition to adiabatic
        surface->setOutsideBoundaryCondition("Adiabatic");
        return surface.get();
      }else{
        auto it = m_workspaceToModelMap.find(target->handle());
        if( it !=  m_workspaceToModelMap.end()){
          if (it->second.optionalCast<Surface>()){
            // this will set other side boundary object on both surfaces
            Surface adjacentSurface = it->second.cast<Surface>();
            surface->setAdjacentSurface(adjacentSurface);
            return surface.get();
          }
        }
      }

    }else{  
      LOG(Error, "OutsideBoundaryConditionObject not yet mapped for object of type " << target->iddObject().name());
    }
  }

  s = workspaceObject.getString(BuildingSurface_DetailedFields::OutsideBoundaryCondition);
  if (s) {
    surface->setOutsideBoundaryCondition(*s);
  }

  return surface.get();
}
TEST_F(IdfFixture, WorkspaceObject_FieldSettingWithHiddenPushes) {
  Workspace scratch(StrictnessLevel::None,IddFileType::EnergyPlus); // Strictness level None

  std::stringstream text;
  text << "ZoneHVAC:HighTemperatureRadiant," << std::endl
       << "  MyRadiantSystem," << std::endl
       << "  MyHVACSchedule," << std::endl
       << "  MyCoreZone," << std::endl
       << "  HeatingDesignCapacity," << std::endl
       << "  Autosize," << std::endl
       << "  ," << std::endl
       << "  ," << std::endl
       << "  Electricity;";
  OptionalIdfObject oObj = IdfObject::load(text.str());
  ASSERT_TRUE(oObj);
  IdfObject idfObject = *oObj;
  OptionalWorkspaceObject w1 = scratch.addObject(idfObject);
  ASSERT_TRUE(w1);
  OptionalWorkspaceObject tObject = scratch.getObject(w1->handle ());
  ASSERT_TRUE(tObject);
  WorkspaceObject object = *tObject;
  EXPECT_EQ(static_cast<unsigned>(8),object.numFields());

  // create schedule object to point to from non-extensible field
  text.str("");
  text << "Schedule:Compact," << std::endl
       << "  AlwaysOn," << std::endl
       << "  ," << std::endl
       << "  For: AllOtherDays," << std::endl
       << "  Until: 24:00," << std::endl
       << "  1.0;";
  oObj = IdfObject::load(text.str());
  ASSERT_TRUE(oObj);
  idfObject = *oObj;
  ASSERT_TRUE(idfObject.iddObject().type() == IddObjectType::Schedule_Compact);
  OptionalWorkspaceObject w2 = scratch.addObject(idfObject);
  ASSERT_TRUE(w2);
  EXPECT_TRUE(object.setPointer(14,w2->handle ()));
  EXPECT_EQ(15u,object.numFields());
  tObject = object.getTarget(14);
  ASSERT_TRUE(tObject);
  EXPECT_TRUE(tObject->handle() == w2->handle());

  // hidden pushing for setting extensible string pointer
  EXPECT_TRUE(object.setString(16,*(tObject->name()))); // should only work at strictness none
  tObject = object.getTarget(16);
  ASSERT_TRUE(tObject);
  EXPECT_TRUE(tObject->handle() == w2->handle());
  EXPECT_EQ(18u,object.numFields());

  // hidden pushing for setting extensible double
  EXPECT_TRUE(object.setDouble(19,0.5));
  EXPECT_EQ(20u,object.numFields());
  OptionalDouble dValue = object.getDouble(19);
  ASSERT_TRUE(dValue);
  EXPECT_NEAR(0.5,*dValue,tol);
  
  // SHOULD NOT BE VALID
  scratch = Workspace(StrictnessLevel::Draft, IddFileType::EnergyPlus); // Non-null data must be valid
  text.str("");
  text << "ZoneHVAC:HighTemperatureRadiant," << std::endl
       << "  MyRadiantSystem," << std::endl
       << "  MyHVACSchedule," << std::endl
       << "  MyCoreZone," << std::endl
       << "  HeatingDesignCapacity," << std::endl
       << "  Autosize," << std::endl
       << "  ," << std::endl
       << "  ," << std::endl
       << "  Electricity;";
  oObj = IdfObject::load(text.str());
  ASSERT_TRUE(oObj);
  idfObject = *oObj;
  w2 = scratch.addObject(idfObject);
  ASSERT_TRUE(w2);
  tObject = scratch.getObject(w2->handle());
  ASSERT_TRUE(tObject);
  object = *tObject;

  // hidden pushing for setting nonextensible double
  EXPECT_FALSE(object.setDouble(9,1.5));
  EXPECT_EQ(8u,object.numFields());
  EXPECT_TRUE(object.setDouble(9,0.6));
  EXPECT_EQ(10u,object.numFields());

  // hidden pushing for setting nonextensible string
  EXPECT_FALSE(object.setString(12,"bad key"));
  EXPECT_EQ(10u,object.numFields());
  EXPECT_TRUE(object.setString(12,"MeanAirTemperature"));
  EXPECT_EQ(13u,object.numFields());

  // hidden pushing for setting nonextensible pointer
  EXPECT_TRUE(object.setString(14,""));
  EXPECT_EQ(15u,object.numFields());

  // hidden pushing for setting extensible string pointer
  EXPECT_FALSE(object.setString(16,"MySurface"));
  EXPECT_EQ(15u,object.numFields());
  EXPECT_TRUE(object.setString(16,""));
  EXPECT_EQ(18u,object.numFields());

  // hidden pushing for setting extensible double
  EXPECT_FALSE(object.setDouble(21,-1.5));
  EXPECT_EQ(18u,object.numFields());
  EXPECT_TRUE(object.setDouble(19,0.5));
  EXPECT_EQ(20u,object.numFields());
}