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_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.

}
 boost::optional<IddObjectType> WorkspaceObjectOrder_Impl::getIddObjectType(
     const Handle& handle) const 
 {
   OS_ASSERT(m_objectGetter);
   OptionalWorkspaceObject object = m_objectGetter(handle);
   if (object) { return object->iddObject().type(); }
   else { return boost::none; }
 } 
TEST_F(EnergyPlusFixture,ReverseTranslator_EmptyIdfFile)
{
  Workspace inWorkspace(StrictnessLevel::Draft,IddFileType::EnergyPlus);
  ReverseTranslator reverseTranslator;
  Model model = reverseTranslator.translateWorkspace(inWorkspace);

  OptionalWorkspaceObject w = inWorkspace.addObject(IdfObject(IddObjectType::Version));
  ASSERT_TRUE(w);
  EXPECT_TRUE(w->setString(VersionFields::VersionIdentifier, "7.0"));

  model = reverseTranslator.translateWorkspace(inWorkspace);

  EXPECT_TRUE(w->setString(VersionFields::VersionIdentifier, "5.0"));

  model = reverseTranslator.translateWorkspace(inWorkspace);
}
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());
}
TEST_F(IdfFixture, WorkspaceObject_GetStringAfterSetStringAddsFields)
{
  // construct empty workspace, and set Idd to be EnergyPlusIdd
  StrictnessLevel level(StrictnessLevel::Draft);
  IddFileType fileType(IddFileType::EnergyPlus);
  Workspace ws(level,fileType);

  OptionalWorkspaceObject w;
  IdfObject idfObj(IddObjectType::SurfaceProperty_ConvectionCoefficients);
  w = ws.addObject(idfObj);
  
  EXPECT_TRUE(w->numNonextensibleFields()==3);
  EXPECT_TRUE(w->setString(w->iddObject().numFields()-1,""));
  EXPECT_TRUE(w->numNonextensibleFields()==11);
  OptionalString s4 = w->getString(4,true);
  EXPECT_TRUE(s4);
  OptionalString s10 = w->getString(10,true);
  EXPECT_TRUE(s10);
}
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());
}
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));
}
OptionalModelObject ReverseTranslator::translateCoilCoolingDXSingleSpeed( const WorkspaceObject & workspaceObject )
{
OptionalModelObject result,temp;
  OptionalSchedule schedule;

  //get the Schedule
  OptionalWorkspaceObject owo = workspaceObject.getTarget(Coil_Cooling_DX_SingleSpeedFields::AvailabilityScheduleName);
  if(!owo)
  {
    LOG(Error, "Error importing object: "
             << workspaceObject.briefDescription()
             << " Can't find Schedule.");
    return result;
  }
  temp = translateAndMapWorkspaceObject(*owo);
  if(temp)
  {
    schedule=temp->optionalCast<Schedule>();
  }

  if( !schedule  )
  {
    LOG(Error,"Error importing object: "
             << workspaceObject.briefDescription()
             <<"Failed to convert iddObject (schedule) into ModelObject. Maybe it does not exist in model yet");
    return result;
  }

  //collect the curves
  owo = workspaceObject.getTarget(Coil_Cooling_DX_SingleSpeedFields::TotalCoolingCapacityFunctionofTemperatureCurveName);
  if(!owo)
  {
    LOG(Error, "Error importing object: "
             << workspaceObject.briefDescription()
             << " Can't find TotalCoolingCapacityFunctionOfTemperatureCurve.");
    return result;
  }
  if( owo->numSources() > 1 )
  {
    owo = owo->workspace().addObject(owo.get());
  }

  temp= translateAndMapWorkspaceObject( *owo );

  if(!temp)
  {
    LOG(Error, "Error importing object: "
             << workspaceObject.briefDescription()
             << " Can't convert workspace curve into a model curve. ");
    return result;
  }
  boost::optional<Curve> tccfot = temp->optionalCast<Curve>();
  if( ! tccfot )
  {
    LOG(Error, "Error importing object: "
             << workspaceObject.briefDescription()
             << " curve is wrong type. ");
    return result;
  }

  owo = workspaceObject.getTarget(Coil_Cooling_DX_SingleSpeedFields::EnergyInputRatioFunctionofTemperatureCurveName);
  if(!owo)
  {
    LOG(Error, "Error importing object: "
             << workspaceObject.briefDescription()
             << " Can't find EnergyInputRatioFunctionofTemperatureCurveName.");
    return result;
  }
  if( owo->numSources() > 1 )
  {
    owo = owo->workspace().addObject(owo.get());
  }
  temp = translateAndMapWorkspaceObject( *owo );
  if(!temp)
  {
    LOG(Error, "Error importing object: "
             << workspaceObject.briefDescription()
             << " Can't convert workspace curve into a model curve. ");
    return result;
  }
  boost::optional<Curve> eirfot = temp->optionalCast<Curve>();
  if( ! eirfot )
  {
    LOG(Error, "Error importing object: "
             << workspaceObject.briefDescription()
             << " curve is wrong type. ");
    return result;
  }


  owo = workspaceObject.getTarget(Coil_Cooling_DX_SingleSpeedFields::TotalCoolingCapacityFunctionofFlowFractionCurveName);
  if(!owo)
  {
    LOG(Error, "Error importing object: "
             << workspaceObject.briefDescription()
             << " Can't find TotalCoolingCapacityFunctionofFlowFractionCurveName.");
    return result;
  }
  if( owo->numSources() > 1 )
  {
    owo = owo->workspace().addObject(owo.get());
  }
  temp = translateAndMapWorkspaceObject( *owo );
  if(!temp)
  {
    LOG(Error, "Error importing object: "
             << workspaceObject.briefDescription()
             << " Can't convert workspace curve into a model curve. ");
    return result;
  }
  boost::optional<Curve> tccfoff = temp->optionalCast<Curve>();
  if( ! tccfoff )
  {
    LOG(Error, "Error importing object: "
             << workspaceObject.briefDescription()
             << " curve is wrong type. ");
    return result;
  }

  owo = workspaceObject.getTarget(Coil_Cooling_DX_SingleSpeedFields::EnergyInputRatioFunctionofFlowFractionCurveName);
  if(!owo)
  {
    LOG(Error, "Error importing object: "
             << workspaceObject.briefDescription()
             << " Can't find EnergyInputRatioFunctionofFlowFractionCurveName.");
    return result;
  }
  if( owo->numSources() > 1 )
  {
    owo = owo->workspace().addObject(owo.get());
  }
  temp = translateAndMapWorkspaceObject( *owo );
  if(!temp)
  {
    LOG(Error, "Error importing object: "
             << workspaceObject.briefDescription()
             << " Can't convert workspace curve into a model curve. ");
    return result;
  }
  boost::optional<Curve> eirfoff = temp->optionalCast<Curve>();
  if( ! eirfoff )
  {
    LOG(Error, "Error importing object: "
             << workspaceObject.briefDescription()
             << " curve is wrong type. ");
    return result;
  }

  owo = workspaceObject.getTarget(Coil_Cooling_DX_SingleSpeedFields::PartLoadFractionCorrelationCurveName);
  if(!owo)
  {
    LOG(Error, "Error importing object: "
             << workspaceObject.briefDescription()
             << " Can't find PartLoadFractionCorrelationCurveName.");
    return result;
  }
  if( owo->numSources() > 1 )
  {
    owo = owo->workspace().addObject(owo.get());
  }
  temp = translateAndMapWorkspaceObject( *owo );
  if(!temp)
  {
    LOG(Error, "Error importing object: "
             << workspaceObject.briefDescription()
             << " Can't convert workspace curve into a model curve. ");
    return result;
  }
  boost::optional<Curve> plfcc = temp->optionalCast<Curve>();
  if( ! plfcc )
  {
    LOG(Error, "Error importing object: "
             << workspaceObject.briefDescription()
             << " curve is wrong type. ");
    return result;
  }


  try {
    CoilCoolingDXSingleSpeed coil(m_model,
                                  *schedule,
                                  *tccfot,
                                  *tccfoff,
                                  *eirfot,
                                  *eirfoff,
                                  *plfcc);

    OptionalString optS = workspaceObject.name();
    if( optS )
    {
      coil.setName( *optS );
    }


    OptionalDouble d = workspaceObject.getDouble(Coil_Cooling_DX_SingleSpeedFields::GrossRatedTotalCoolingCapacity);
    if(d)
    {
      coil.setRatedTotalCoolingCapacity(*d);
    }

    d=workspaceObject.getDouble(Coil_Cooling_DX_SingleSpeedFields::GrossRatedSensibleHeatRatio);
    if(d)
    {
      coil.setRatedSensibleHeatRatio(*d);
    }

    d=workspaceObject.getDouble(Coil_Cooling_DX_SingleSpeedFields::GrossRatedCoolingCOP);
    if(d)
    {
      coil.setRatedCOP(*d);
    }

    d=workspaceObject.getDouble(Coil_Cooling_DX_SingleSpeedFields::RatedAirFlowRate);
    if(d)
    {
      coil.setRatedAirFlowRate(*d);
    }

    d=workspaceObject.getDouble(Coil_Cooling_DX_SingleSpeedFields::RatedEvaporatorFanPowerPerVolumeFlowRate);
    if(d)
    {
      coil.setRatedEvaporatorFanPowerPerVolumeFlowRate(*d);
    }

    d=workspaceObject.getDouble(Coil_Cooling_DX_SingleSpeedFields::NominalTimeforCondensateRemovaltoBegin);
    if(d)
    {
      coil.setNominalTimeForCondensateRemovalToBegin(*d);
    }

    d=workspaceObject.getDouble(Coil_Cooling_DX_SingleSpeedFields::RatioofInitialMoistureEvaporationRateandSteadyStateLatentCapacity);
    if(d)
    {
      coil.setRatioOfInitialMoistureEvaporationRateAndSteadyStateLatentCapacity(*d);
    }

    d=workspaceObject.getDouble(Coil_Cooling_DX_SingleSpeedFields::MaximumCyclingRate);
    if(d)
    {
      coil.setMaximumCyclingRate(*d);
    }

    d=workspaceObject.getDouble(Coil_Cooling_DX_SingleSpeedFields::LatentCapacityTimeConstant);
    if(d)
    {
      coil.setLatentCapacityTimeConstant(*d);
    }

    optS=workspaceObject.getString(Coil_Cooling_DX_SingleSpeedFields::CondenserAirInletNodeName);
    if(optS)
    {
      coil.setCondenserAirInletNodeName(*optS);
    }

    optS = workspaceObject.getString(Coil_Cooling_DX_SingleSpeedFields::CondenserType);
    if(optS)
    {
      coil.setCondenserType(*optS);
    }

    d=workspaceObject.getDouble(Coil_Cooling_DX_SingleSpeedFields::EvaporativeCondenserEffectiveness);
    if(d)
    {
      coil.setEvaporativeCondenserEffectiveness(*d);
    }

    d=workspaceObject.getDouble(Coil_Cooling_DX_SingleSpeedFields::EvaporativeCondenserAirFlowRate);
    if(d)
    {
      coil.setEvaporativeCondenserAirFlowRate(*d);
    }

    d=workspaceObject.getDouble(Coil_Cooling_DX_SingleSpeedFields::EvaporativeCondenserPumpRatedPowerConsumption);
    if(d)
    {
      coil.setEvaporativeCondenserPumpRatedPowerConsumption(*d);
    }

    d=workspaceObject.getDouble(Coil_Cooling_DX_SingleSpeedFields::CrankcaseHeaterCapacity);
    if(d)
    {
      coil.setCrankcaseHeaterCapacity(*d);
    }

    d=workspaceObject.getDouble(Coil_Cooling_DX_SingleSpeedFields::MaximumOutdoorDryBulbTemperatureforCrankcaseHeaterOperation);
    if(d)
    {
      coil.setMaximumOutdoorDryBulbTemperatureForCrankcaseHeaterOperation(*d);
    }

    result=coil;

  }
  catch (std::exception& e) {
    LOG(Error,"Could not reverse translate " << workspaceObject.briefDescription()
        << ", because " << e.what() << ".");
  }
  return result;
}
  // remove all spaces and add a new one
  virtual bool run(Model& model,
                   OSRunner& runner,
                   const std::map<std::string, OSArgument>& user_arguments) const
  {
    ModelUserScript::run(model,runner,user_arguments); // initializes runner

    // calls runner.registerAttribute for 'lights_definition' and 'multiplier'
    if (!runner.validateUserArguments(arguments(model),user_arguments)) {
      return false;
    }

    // lights_definition argument value will be object handle
    Handle h = toUUID(runner.getStringArgumentValue("lights_definition",user_arguments));

    OptionalWorkspaceObject wo = model.getObject(h);
    if (!wo) {
      std::stringstream ss;
      ss << "Object " << toString(h) << " not found in model.";
      runner.registerError(ss.str());
      return false;
    }

    OptionalLightsDefinition lightsDef = wo->optionalCast<LightsDefinition>();
    if (!lightsDef) {
      std::stringstream ss;
      ss << wo->briefDescription() << " is not a LightsDefinition.";
      runner.registerError(ss.str());
      return false;
    }
    // save name of lights definition
    runner.registerValue("lights_definition_name",lightsDef->name().get());

    if (!(lightsDef->designLevelCalculationMethod() == "Watts/Area")) {
      std::stringstream ss;
      ss << "This measure only applies to lights definitions that are in units of Watts/Area. ";
      ss << lightsDef->briefDescription() << " is in units of ";
      ss << lightsDef->designLevelCalculationMethod() << ".";
      runner.registerAsNotApplicable(ss.str());
      return true;
    }

    double multiplier = runner.getDoubleArgumentValue("multiplier",user_arguments);

    if (multiplier < 0.0) {
      std::stringstream ss;
      ss << "The lighting power density multiplier must be greater than or equal to 0. ";
      ss << "Instead, it is " << toString(multiplier) << ".";
      runner.registerError(ss.str());
      return false;
    }

    double originalValue = lightsDef->wattsperSpaceFloorArea().get();
    double newValue = multiplier * originalValue;

    lightsDef->setWattsperSpaceFloorArea(newValue);

    // register effects of this measure

    // human-readable
    std::stringstream ss;
    ss << "The lighting power density of " << lightsDef->briefDescription();
    ss << ", which is used by " << lightsDef->quantity() << " instances covering ";
    ss << lightsDef->floorArea() << " m^2 of floor area, was " << originalValue << ".";
    runner.registerInitialCondition(ss.str()); ss.str("");
    ss << "The lighting power density of " << lightsDef->briefDescription();
    ss << " has been changed to " << newValue << ".";
    runner.registerFinalCondition(ss.str()); ss.str("");

    // machine-readable
    runner.registerValue("lpd_in","Input Lighting Power Density",originalValue,"W/m^2");
    runner.registerValue("lpd_out","Output Lighting Power Density",newValue,"W/m^2");
    runner.registerValue("lights_definition_num_instances",lightsDef->quantity());
    runner.registerValue("lights_definition_floor_area",
                         "Floor Area using this Lights Definition (SI)",
                         lightsDef->floorArea(),
                         "m^2");
    runner.registerValue("lights_definition_floor_area_ip",
                         "Floor Area using this Lights Definition (IP)",
                         convert(lightsDef->floorArea(),"m^2","ft^2").get(),
                         "ft^2");

    return true;
  }
OptionalModelObject ReverseTranslator::translateSizingZone( const WorkspaceObject & workspaceObject )
{
    boost::optional<WorkspaceObject> target = workspaceObject.getTarget(Sizing_ZoneFields::ZoneorZoneListName);

    std::vector<ThermalZone> thermalZones;

    if( target ) {

        // just one thermal zone
        boost::optional<ModelObject> mo;
        if (target->iddObject().type() == IddObjectType::Zone) {
            mo = translateAndMapWorkspaceObject(target.get());
            if( mo ) {
                if( boost::optional<Space> space = mo->optionalCast<Space>() ) {
                    boost::optional<ThermalZone> thermalZone = space->thermalZone();
                    if (thermalZone) {
                        thermalZones.push_back(*thermalZone);
                    }
                }
            }
        } else if (target->iddObject().type() == IddObjectType::ZoneList) {

            // get all thermal zones in zone list
            for (const IdfExtensibleGroup& idfGroup : target->extensibleGroups()) {
                WorkspaceExtensibleGroup workspaceGroup = idfGroup.cast<WorkspaceExtensibleGroup>();
                OptionalWorkspaceObject owo = workspaceGroup.getTarget(0);
                if (owo) {
                    mo = translateAndMapWorkspaceObject(owo.get());
                    if( mo ) {
                        if( boost::optional<Space> space = mo->optionalCast<Space>() ) {
                            boost::optional<ThermalZone> thermalZone = space->thermalZone();
                            if (thermalZone) {
                                thermalZones.push_back(*thermalZone);
                            }
                        }
                    }
                }
            }
        }
    }

    if(thermalZones.empty())
    {
        LOG(Error, "Error importing object: "
            << workspaceObject.briefDescription()
            << " Can't find associated ThermalZone(s).");

        return boost::none;
    }

    boost::optional<ModelObject> result;
    for (ThermalZone thermalZone : thermalZones) {

        // sizing zone is constructed in thermal zone ctor
        openstudio::model::SizingZone sizingZone = thermalZone.sizingZone();

        // return first sizing zone
        if (!result) {
            result = sizingZone;
        }

        boost::optional<std::string> s;
        boost::optional<double> value;

        // ZoneCoolingDesignSupplyAirTemperature

        value = workspaceObject.getDouble(Sizing_ZoneFields::ZoneCoolingDesignSupplyAirTemperature);
        if( value )
        {
            sizingZone.setZoneCoolingDesignSupplyAirTemperature(value.get());
        }

        // ZoneHeatingDesignSupplyAirTemperature

        value = workspaceObject.getDouble(Sizing_ZoneFields::ZoneHeatingDesignSupplyAirTemperature);
        if( value )
        {
            sizingZone.setZoneHeatingDesignSupplyAirTemperature(value.get());
        }

        // ZoneCoolingDesignSupplyAirHumidityRatio

        value = workspaceObject.getDouble(Sizing_ZoneFields::ZoneHeatingDesignSupplyAirHumidityRatio);
        if( value )
        {
            sizingZone.setZoneHeatingDesignSupplyAirHumidityRatio(value.get());
        }

        // ZoneHeatingDesignSupplyAirHumidityRatio

        value = workspaceObject.getDouble(Sizing_ZoneFields::ZoneHeatingDesignSupplyAirHumidityRatio);
        if( value )
        {
            sizingZone.setZoneHeatingDesignSupplyAirHumidityRatio(value.get());
        }

        // DesignSpecificationOutdoorAirObjectName

        target = workspaceObject.getTarget(Sizing_ZoneFields::DesignSpecificationOutdoorAirObjectName);
        if (target) {
            OptionalModelObject mo = translateDesignSpecificationOutdoorAir(*target);
            if (mo) {
                if (mo->optionalCast<DesignSpecificationOutdoorAir>()) {
                    std::vector<Space> spaces = thermalZone.spaces();
                    OS_ASSERT(spaces.size() == 1);
                    spaces[0].setDesignSpecificationOutdoorAir(mo->cast<DesignSpecificationOutdoorAir>());
                }
            }
        }

        // ZoneHeatingSizingFactor

        value = workspaceObject.getDouble(Sizing_ZoneFields::ZoneHeatingSizingFactor);
        if( value )
        {
            sizingZone.setZoneHeatingSizingFactor(value.get());
        }

        // ZoneCoolingSizingFactor

        value = workspaceObject.getDouble(Sizing_ZoneFields::ZoneCoolingSizingFactor);
        if( value )
        {
            sizingZone.setZoneCoolingSizingFactor(value.get());
        }

        // CoolingDesignAirFlowMethod

        s = workspaceObject.getString(Sizing_ZoneFields::CoolingDesignAirFlowMethod);
        if( s )
        {
            sizingZone.setCoolingDesignAirFlowMethod(s.get());
        }

        // CoolingDesignAirFlowRate

        value = workspaceObject.getDouble(Sizing_ZoneFields::CoolingDesignAirFlowRate);
        if( value )
        {
            sizingZone.setCoolingDesignAirFlowRate(value.get());
        }

        // CoolingMinimumAirFlowperZoneFloorArea

        value = workspaceObject.getDouble(Sizing_ZoneFields::CoolingMinimumAirFlowperZoneFloorArea);
        if( value )
        {
            sizingZone.setCoolingMinimumAirFlowperZoneFloorArea(value.get());
        }

        // CoolingMinimumAirFlow

        value = workspaceObject.getDouble(Sizing_ZoneFields::CoolingMinimumAirFlow);
        if( value )
        {
            sizingZone.setCoolingMinimumAirFlow(value.get());
        }

        // CoolingMinimumAirFlowFraction

        value = workspaceObject.getDouble(Sizing_ZoneFields::CoolingMinimumAirFlowFraction);
        if( value )
        {
            sizingZone.setCoolingMinimumAirFlowFraction(value.get());
        }

        // HeatingDesignAirFlowMethod

        s = workspaceObject.getString(Sizing_ZoneFields::HeatingDesignAirFlowMethod);
        if( s )
        {
            sizingZone.setHeatingDesignAirFlowMethod(s.get());
        }

        // HeatingDesignAirFlowRate

        value = workspaceObject.getDouble(Sizing_ZoneFields::HeatingDesignAirFlowRate);
        if( value )
        {
            sizingZone.setHeatingDesignAirFlowRate(value.get());
        }

        // HeatingMaximumAirFlowperZoneFloorArea

        value = workspaceObject.getDouble(Sizing_ZoneFields::HeatingMaximumAirFlowperZoneFloorArea);
        if( value )
        {
            sizingZone.setHeatingMaximumAirFlowperZoneFloorArea(value.get());
        }

        // HeatingMaximumAirFlow

        value = workspaceObject.getDouble(Sizing_ZoneFields::HeatingMaximumAirFlow);
        if( value )
        {
            sizingZone.setHeatingMaximumAirFlow(value.get());
        }

        // HeatingMaximumAirFlowFraction

        value = workspaceObject.getDouble(Sizing_ZoneFields::HeatingMaximumAirFlowFraction);
        if( value )
        {
            sizingZone.setHeatingMaximumAirFlowFraction(value.get());
        }

        //DesignSpecification_ZoneAirDistribution

        boost::optional<WorkspaceObject> _designSpecification
            = workspaceObject.getTarget(Sizing_ZoneFields::DesignSpecificationZoneAirDistributionObjectName);

        if( _designSpecification )
        {
            // ZoneAirDistributionEffectivenessinCoolingMode

            value = _designSpecification->getDouble(
                        DesignSpecification_ZoneAirDistributionFields::ZoneAirDistributionEffectivenessinCoolingMode);
            if( value )
            {
                sizingZone.setDesignZoneAirDistributionEffectivenessinCoolingMode(value.get());
            }

            // ZoneAirDistributionEffectivenessinHeatingMode

            value = _designSpecification->getDouble(
                        DesignSpecification_ZoneAirDistributionFields::ZoneAirDistributionEffectivenessinHeatingMode);
            if( value )
            {
                sizingZone.setDesignZoneAirDistributionEffectivenessinHeatingMode(value.get());
            }
        }

    }

    return result;
}
TEST_F(IdfFixture,WorkspaceObjectWatcher_RelationshipFieldChanges)
{
  IdfObject object(IddObjectType::DaylightingDevice_Tubular);
  Workspace workspace(StrictnessLevel::Draft, IddFileType::EnergyPlus);

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

  // add zone objects
  object = IdfObject(IddObjectType::Zone);
  owo = workspace.addObject(object);
  ASSERT_TRUE(owo);
  EXPECT_EQ("Zone 1",owo->name().get());
  owo = workspace.addObject(object);
  ASSERT_TRUE(owo);
  EXPECT_EQ("Zone 2",owo->name().get());
  owo = workspace.addObject(object);
  ASSERT_TRUE(owo);
  EXPECT_EQ("Zone 3",owo->name().get());
  EXPECT_FALSE(watcher.dirty());

  vector<string> vals;
  vals.push_back("Zone 1");
  vals.push_back("1.0");
  IdfExtensibleGroup eg = tdd.pushExtensibleGroup(vals);
  ASSERT_FALSE(eg.empty());
  EXPECT_TRUE(watcher.dirty());
  EXPECT_TRUE(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());

  EXPECT_TRUE(eg.setString(0,"Zone 2"));
  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());

  EXPECT_TRUE(eg.setDouble(1,2.0));
  EXPECT_TRUE(watcher.dirty());
  EXPECT_TRUE(watcher.dataChanged());
  EXPECT_FALSE(watcher.nameChanged());
  EXPECT_FALSE(watcher.relationshipChanged());
  watcher.clearState();
  EXPECT_FALSE(watcher.dirty());
  EXPECT_FALSE(watcher.dataChanged());
  EXPECT_FALSE(watcher.nameChanged());
  EXPECT_FALSE(watcher.relationshipChanged());
}
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();
}
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();
}
TEST_F(IdfFixture, WorkspaceObject_Lights) {

  Workspace workspace(epIdfFile, StrictnessLevel::Draft);
  OptionalWorkspaceObject light = workspace.getObjectByTypeAndName(IddObjectType::Lights, "SPACE1-1 Lights 1");
  ASSERT_TRUE(light);

  OptionalString lightsZoneName = light->getString(LightsFields::ZoneorZoneListName);
  ASSERT_TRUE(lightsZoneName);

  OptionalString lightsScheduleName = light->getString(LightsFields::ScheduleName);
  ASSERT_TRUE(lightsScheduleName);

  OptionalWorkspaceObject zone = light->getTarget(LightsFields::ZoneorZoneListName);
  ASSERT_TRUE(zone);

  OptionalString zoneName = zone->getString(ZoneFields::Name);
  ASSERT_TRUE(zoneName);
  EXPECT_EQ(*lightsZoneName, *zoneName);

  OptionalWorkspaceObject schedule = light->getTarget(LightsFields::ScheduleName);
  ASSERT_TRUE(schedule);
  EXPECT_EQ(IddObjectType::Schedule_Compact, schedule->iddObject().type().value());

  OptionalString scheduleName = schedule->getString(Schedule_CompactFields::Name);
  ASSERT_TRUE(scheduleName);
  EXPECT_EQ(*lightsScheduleName, *scheduleName);

  // now change name of zone and schedule and make sure string reference in light change as well

  EXPECT_TRUE(zone->setString(ZoneFields::Name, "New Zone Name"));
  zoneName = zone->getString(ZoneFields::Name);
  ASSERT_TRUE(zoneName);
  EXPECT_EQ("New Zone Name", *zoneName);

  lightsZoneName = light->getString(LightsFields::ZoneorZoneListName);
  ASSERT_TRUE(lightsZoneName);
  EXPECT_EQ(*lightsZoneName, *zoneName);

  EXPECT_TRUE(zone->createName());
  zoneName = zone->getString(ZoneFields::Name);
  ASSERT_TRUE(zoneName);
  EXPECT_EQ("Zone 1",*zoneName);

  lightsZoneName = light->getString(LightsFields::ZoneorZoneListName);
  ASSERT_TRUE(lightsZoneName);
  EXPECT_EQ(*lightsZoneName, *zoneName);

  EXPECT_TRUE(schedule->setString(Schedule_CompactFields::Name, "New Schedule Name"));
  scheduleName = schedule->getString(Schedule_CompactFields::Name);
  ASSERT_TRUE(scheduleName);
  EXPECT_EQ("New Schedule Name", *scheduleName);

  lightsScheduleName = light->getString(LightsFields::ScheduleName);
  ASSERT_TRUE(lightsScheduleName);
  EXPECT_EQ(*lightsScheduleName, *scheduleName);

}