boost::optional<IdfObject> ForwardTranslator::translateAvailabilityManagerNightCycle(
    AvailabilityManagerNightCycle & modelObject)
{
  IdfObject idfObject(IddObjectType::AvailabilityManager_NightCycle);
  m_idfObjects.push_back(idfObject);

  boost::optional<AirLoopHVAC> airLoopHVAC;
  if( auto loop = modelObject.loop() ) {
    airLoopHVAC = loop->optionalCast<model::AirLoopHVAC>();
  }

  {
    auto schedule = modelObject.model().alwaysOnDiscreteSchedule();
    idfObject.setString(AvailabilityManager_NightCycleFields::ApplicabilityScheduleName,schedule.name().get());
  }

  if( airLoopHVAC ) {
    // Fan schedules are set to match the availabilitySchedule in the translator
    idfObject.setString(AvailabilityManager_NightCycleFields::FanScheduleName,airLoopHVAC->availabilitySchedule().name().get());
  }

  // TODO: @kbenne, we don't even translate the AVM:NightCycle if it's not on an airloop anyways
  // Translation is triggered from the AirLoopHVAC itself

  if( auto s = modelObject.name() ) {
    idfObject.setName(*s);
  }


  {
    auto value = modelObject.thermostatTolerance();
    idfObject.setDouble(AvailabilityManager_NightCycleFields::ThermostatTolerance,value);
  }


  // Cycling Run Time and Cycling Run Time Control Type
  {
    double runtime = modelObject.cyclingRunTime();
    idfObject.setDouble(AvailabilityManager_NightCycleFields::CyclingRunTime, runtime);
    std::string cycRTCType = modelObject.cyclingRunTimeControlType();

    if (istringEqual(cycRTCType, "Thermostat")) {
      LOG(Info, "With a Cycling Run Time Control Type set to '" << cycRTCType
          << "', the entered Cycling Run Time of '" << runtime << "' seconds will be ignored for "
          << modelObject.briefDescription());
    }
    idfObject.setString(AvailabilityManager_NightCycleFields::CyclingRunTimeControlType, cycRTCType);
  }


  // We'll use that later to default the control zone lists
  std::string controlType = modelObject.controlType();
  idfObject.setString(AvailabilityManager_NightCycleFields::ControlType, controlType);

  // Zone Lists
  std::vector<ThermalZone> controlThermalZones = modelObject.controlThermalZones();
  std::vector<ThermalZone> coolingControlThermalZones = modelObject.coolingControlThermalZones();
  std::vector<ThermalZone> heatingControlThermalZones = modelObject.heatingControlThermalZones();
  std::vector<ThermalZone> heatingZoneFansOnlyThermalZones = modelObject.heatingZoneFansOnlyThermalZones();

  // Size of Zone Lists for convenience
  auto n_controlThermalZones = controlThermalZones.size();
  auto n_coolingControlThermalZones = coolingControlThermalZones.size();
  auto n_heatingControlThermalZones = heatingControlThermalZones.size();
  auto n_heatingZoneFansOnlyThermalZones = heatingZoneFansOnlyThermalZones.size();

  // Boolean to decide whether defaulting a given ZoneList to all zones served by system is needed or not
  bool default_controlThermalZones = false;
  bool default_coolingControlThermalZones = false;
  bool default_heatingControlThermalZones = false;
  bool default_heatingZoneFansOnlyThermalZones = false;

  // Main logic to warn user and decide if defaulting is needed based on the Control Type entered
  if ( istringEqual(controlType, "StayOff") ||
       istringEqual(controlType, "CycleOnAny") ||
       istringEqual(controlType, "CycleOnAnyZoneFansOnly") ) {

    // All Control Zone Lists are ignored
    if ((  n_controlThermalZones
         + n_coolingControlThermalZones
         + n_heatingControlThermalZones
         + n_heatingZoneFansOnlyThermalZones) > 0 ) {
      LOG(Info, "All Control Zone Lists will be ignored for " << modelObject.briefDescription()
            << " due to the Control Type of '" << controlType << "'.");
    }

  } else if ( istringEqual(controlType, "CycleOnControlZone") ) {

    // Only the controlThermalZones isn't ignored
    if ((  n_coolingControlThermalZones
         + n_heatingControlThermalZones
         + n_heatingZoneFansOnlyThermalZones) > 0 ) {
      LOG(Info, "All Control Zone Lists other than 'Control Zone List' will be ignored for " << modelObject.briefDescription()
            << " due to the Control Type of '" << controlType << "'.");
    }

    // Check if need to default the controlThermalZones
    // Another option might have been to switch the control Type to CycleOnAny
    if (n_controlThermalZones == 0) {
      default_controlThermalZones = true;
    }

  } else if ( istringEqual(controlType, "CycleOnAnyCoolingZone") ) {

    // Only the coolingControlThermalZones isn't ignored
    if ((  n_controlThermalZones
         + n_heatingControlThermalZones
         + n_heatingZoneFansOnlyThermalZones) > 0 ) {
      LOG(Info, "All Control Zone Lists  other than 'Cooling Control Zone List' will be ignored for " << modelObject.briefDescription()
            << " due to the Control Type of '" << controlType << "'.");
    }

    // Check if need to default
    if (n_coolingControlThermalZones == 0) {
      default_coolingControlThermalZones = true;
    }


  } else if ( istringEqual(controlType, "CycleOnAnyHeatingZone") ) {

    // Only the heatingControlThermalZones, and heatingZoneFansOnlyThermalZones aren't ignored
    if (( n_controlThermalZones
          + n_coolingControlThermalZones) > 0 ) {
      LOG(Info, "All Control Zone Lists  other than 'Heating Control Zone List' and optionally "
                "'Heating Zone Fans Only Zone List' will be ignored for " << modelObject.briefDescription()
                << " due to the Control Type of '" << controlType << "'.");
    }

    // Check if need to default
    if (n_heatingControlThermalZones == 0) {
      default_heatingControlThermalZones = true;
    }
    // TODO: @kbenne
    // here we should't default the heatingZoneFansOnlyThermalZones
    // I view that as an option to enable cycling on zone fans only or not,
    // while you only get central heating operation (not possible with CycleOnAny)

  } else if ( istringEqual(controlType, "CycleOnAnyHeatingZoneFansOnly") ) {

    // Only the heatingZoneFansOnlyThermalZones isn't ignored
    if ((  n_controlThermalZones
          + n_coolingControlThermalZones
          + n_heatingControlThermalZones) > 0 ) {
      LOG(Info, "All Control Zone Lists  other than 'Heating Zone Fans Only Zone List' will be ignored for "
                << modelObject.briefDescription()
                << " due to the Control Type of '" << controlType << "'.");
    }

    // Check if need to default
    if (n_heatingZoneFansOnlyThermalZones == 0) {
      default_heatingZoneFansOnlyThermalZones = true;
    }


  } else if ( istringEqual(controlType, "CycleOnAnyCoolingOrHeatingZone") ) {

    // Only the coolingControlThermalZones, coolingControlThermalZones, and optionally heatingZoneFansOnlyThermalZones aren't ignored
      if ( n_controlThermalZones > 0 ) {
      LOG(Info, "All Control Zone Lists  other than 'Heating Control Zone List', 'Cooling Control Zone List' and optionally "
                "'Heating Zone Fans Only Zone List' will be ignored for " << modelObject.briefDescription()
                << " due to the Control Type of '" << controlType << "'.");
    }

    // Check if need to default
    if (n_heatingControlThermalZones == 0) {
      default_heatingControlThermalZones = true;
    }
    if (n_coolingControlThermalZones == 0) {
      default_coolingControlThermalZones = true;
    }
    // TODO: @kbenne
    // Here we should default the heatingZoneFansOnlyThermalZones
    // Because otherwise the user could have just chosen "CycleOnAny"
    if (n_heatingZoneFansOnlyThermalZones == 0) {
      default_heatingZoneFansOnlyThermalZones = true;
    }


  } else {
    // should never get there, unless a new ControlType is added later...
    LOG_AND_THROW("Unknown Control Type of '" << controlType << "' for " << modelObject.briefDescription());
  } // End of Main logic to warn user and decide if defaulting is needed based on the Control Type entered


  // Whether a Zone List will actually be unused or not, we still translate it (perhaps the user is going to use the IDF later)

  // Control Thermal Zones
  {
    // Create a ZoneList, and populate it
    IdfObject zoneList(IddObjectType::ZoneList);
    std::string zoneListName = modelObject.name().get() + " Control Zones List";
    zoneList.setName(zoneListName);
    bool write_zonelist;

    if (default_controlThermalZones) {
      // Get all zones served by the AirLoop attached to it
      if( airLoopHVAC ) {
        for( const ThermalZone & tz : airLoopHVAC->thermalZones() ) {
          auto eg = zoneList.pushExtensibleGroup();
          eg.setString(ZoneListExtensibleFields::ZoneName, tz.name().get());
        }
        LOG(Warn, "Defaulting the Control Zone List to all zones served by the AirLoopHVAC attached to "
               << modelObject.briefDescription());
        write_zonelist = true;
      } else {
        // Note: we never get here because the translation of the AVM:NightCyle isn't done if no AirLoopHVAC attached
        LOG(Error, "Control Zone List is expected for " << modelObject.briefDescription()
               << " but it cannot be defaulted because it isn't on an AirLoopHVAC");
        write_zonelist = false;
      }
    } else {
      if (n_controlThermalZones > 0) {
        if (n_controlThermalZones == 1) {
          // If only one, just write the thermalZone name directly
          write_zonelist = false;
          idfObject.setString(AvailabilityManager_NightCycleFields::ControlZoneorZoneListName,
                              controlThermalZones[0].name().get());
        } else {
          // More than one, we indeed create a zone list
          for (const ThermalZone& tz: controlThermalZones) {
            auto eg = zoneList.pushExtensibleGroup();
            eg.setString(ZoneListExtensibleFields::ZoneName, tz.name().get());
          }
          write_zonelist = true;
        }
      } else {
        // No zones = no zonelist
        write_zonelist = false;
      }
    }
    // Write ZoneList to the IDF and set the AVM ZoneList field to it only if something meaningful happened
    if (write_zonelist) {
      idfObject.setString(AvailabilityManager_NightCycleFields::ControlZoneorZoneListName, zoneListName);
      m_idfObjects.push_back(zoneList);
    }
  }


  // Cooling Control Thermal Zones
  {
    // Create a ZoneList, and populate it
    IdfObject zoneList(IddObjectType::ZoneList);
    std::string zoneListName = modelObject.name().get() + " Cooling Control Zones List";
    zoneList.setName(zoneListName);
    bool write_zonelist;

    if (default_coolingControlThermalZones) {
      // Get all zones served by the AirLoop attached to it
      if( airLoopHVAC ) {
        for( const ThermalZone & tz : airLoopHVAC->thermalZones() ) {
          auto eg = zoneList.pushExtensibleGroup();
          eg.setString(ZoneListExtensibleFields::ZoneName, tz.name().get());
        }
        LOG(Warn, "Defaulting the Cooling Control Zone List to all zones served by the AirLoopHVAC attached to "
               << modelObject.briefDescription());
        write_zonelist = true;
      } else {
        LOG(Error, "Cooling Control Zone List is expected for " << modelObject.briefDescription()
               << " but it cannot be defaulted because it isn't on an AirLoopHVAC");
        write_zonelist = false;
      }
    } else {
      if (n_coolingControlThermalZones > 0) {
        if (n_coolingControlThermalZones == 1) {
          // If only one, just write the thermalZone name directly
          write_zonelist = false;
          idfObject.setString(AvailabilityManager_NightCycleFields::CoolingControlZoneorZoneListName,
                              coolingControlThermalZones[0].name().get());
        } else {
          // More than one, we indeed create a zone list
          for (const ThermalZone& tz: coolingControlThermalZones) {
            auto eg = zoneList.pushExtensibleGroup();
            eg.setString(ZoneListExtensibleFields::ZoneName, tz.name().get());
          }
          write_zonelist = true;
        }
      } else {
        // No zones = no zonelist
        write_zonelist = false;
      }
    }
    // Write ZoneList to the IDF and set the AVM ZoneList field to it only if something meaningful happened
    if (write_zonelist) {
      idfObject.setString(AvailabilityManager_NightCycleFields::CoolingControlZoneorZoneListName, zoneListName);
      m_idfObjects.push_back(zoneList);
    }
  }


  // Heating Control Thermal Zones
  {
    // Create a ZoneList, and populate it
    IdfObject zoneList(IddObjectType::ZoneList);
    std::string zoneListName = modelObject.name().get() + " Heating Control Zones List";
    zoneList.setName(zoneListName);
    bool write_zonelist;

    if (default_heatingControlThermalZones) {
      // Get all zones served by the AirLoop attached to it
      if( airLoopHVAC ) {
        for( const ThermalZone & tz : airLoopHVAC->thermalZones() ) {
          auto eg = zoneList.pushExtensibleGroup();
          eg.setString(ZoneListExtensibleFields::ZoneName, tz.name().get());
        }
        LOG(Warn, "Defaulting the Heating Control Zone List to all zones served by the AirLoopHVAC attached to "
               << modelObject.briefDescription());
        write_zonelist = true;
      } else {
        LOG(Error, "Cooling Heating Zone List is expected for " << modelObject.briefDescription()
               << " but it cannot be defaulted because it isn't on an AirLoopHVAC");
        write_zonelist = false;
      }
    } else {
      if (n_heatingControlThermalZones > 0) {
        if (n_heatingControlThermalZones == 1) {
          // If only one, just write the thermalZone name directly
          write_zonelist = false;
          idfObject.setString(AvailabilityManager_NightCycleFields::HeatingControlZoneorZoneListName,
                              heatingControlThermalZones[0].name().get());
        } else {
          // More than one, we indeed create a zone list

          for (const ThermalZone& tz: heatingControlThermalZones) {
            auto eg = zoneList.pushExtensibleGroup();
            eg.setString(ZoneListExtensibleFields::ZoneName, tz.name().get());
          }
          write_zonelist = true;
        }
      } else {
        // No zones = no zonelist
        write_zonelist = false;
      }
    }
    // Write ZoneList to the IDF and set the AVM ZoneList field to it only if something meaningful happened
    if (write_zonelist) {
      idfObject.setString(AvailabilityManager_NightCycleFields::HeatingControlZoneorZoneListName, zoneListName);
      m_idfObjects.push_back(zoneList);
    }
  }

  // Heating Zone Fans Only Thermal Zones
  {
    // Create a ZoneList, and populate it
    IdfObject zoneList(IddObjectType::ZoneList);
    std::string zoneListName = modelObject.name().get() + " Heating Zone Fans Only Zones List";
    zoneList.setName(zoneListName);
    bool write_zonelist;

    if (default_heatingZoneFansOnlyThermalZones) {
      // Get all zones served by the AirLoop attached to it
      if( airLoopHVAC ) {
        for( const ThermalZone & tz : airLoopHVAC->thermalZones() ) {
          auto eg = zoneList.pushExtensibleGroup();
          eg.setString(ZoneListExtensibleFields::ZoneName, tz.name().get());
        }
        LOG(Warn, "Defaulting the Heating Zone Fans Only Zones List to all zones served by the AirLoopHVAC attached to "
               << modelObject.briefDescription());
        write_zonelist = true;
      } else {
        LOG(Error, "Heating Zone Fans Only Zones List is expected for " << modelObject.briefDescription()
               << " but it cannot be defaulted because it isn't on an AirLoopHVAC");
        write_zonelist = false;
      }
    } else {
      if (n_heatingZoneFansOnlyThermalZones > 0) {
        if (n_heatingZoneFansOnlyThermalZones == 1) {
          // If only one, just write the thermalZone name directly
          write_zonelist = false;
          idfObject.setString(AvailabilityManager_NightCycleFields::HeatingZoneFansOnlyZoneorZoneListName,
                              heatingZoneFansOnlyThermalZones[0].name().get());
        } else {
          // More than one, we indeed create a zone list
          for (const ThermalZone& tz: heatingZoneFansOnlyThermalZones) {
            auto eg = zoneList.pushExtensibleGroup();
            eg.setString(ZoneListExtensibleFields::ZoneName, tz.name().get());
          }
          write_zonelist = true;
        }
      } else {
        // No zones = no zonelist
        write_zonelist = false;
      }
    }
    // Write ZoneList to the IDF and set the AVM ZoneList field to it only if something meaningful happened
    if (write_zonelist) {
      idfObject.setString(AvailabilityManager_NightCycleFields::HeatingZoneFansOnlyZoneorZoneListName, zoneListName);
      m_idfObjects.push_back(zoneList);
    }
  }

  return idfObject;
}
boost::optional<IdfObject> ForwardTranslator::translateAvailabilityManagerOptimumStart( 
    AvailabilityManagerOptimumStart & modelObject)
{
  IdfObject idfObject(IddObjectType::AvailabilityManager_OptimumStart);
  m_idfObjects.push_back(idfObject);

  boost::optional<AirLoopHVAC> airLoopHVAC;
  if( auto loop = modelObject.loop() ) {
    airLoopHVAC = loop->optionalCast<model::AirLoopHVAC>();
  }

  // Name
  if( auto s = modelObject.name() ) {
    idfObject.setName(*s);
  }

  // ApplicabilityScheduleName
  {
    auto schedule = modelObject.applicabilitySchedule();
    idfObject.setString(AvailabilityManager_OptimumStartFields::ApplicabilityScheduleName,schedule.name().get());
  }

  // FanScheduleName
  if( airLoopHVAC ) {
    // Fan schedules are set to match the availabilitySchedule in the translator
    idfObject.setString(AvailabilityManager_OptimumStartFields::FanScheduleName,airLoopHVAC->availabilitySchedule().name().get());
  }

  // ControlType
  auto controlType = modelObject.controlType();
  idfObject.setString(AvailabilityManager_OptimumStartFields::ControlType,controlType);

  if( istringEqual(controlType,"ControlZone") ) {
    if( auto zone = modelObject.controlZone() ) {
      idfObject.setString(AvailabilityManager_OptimumStartFields::ControlZoneName,zone->name().get());
    }
  } else if( istringEqual(controlType,"MaximumofZoneList") ) {
    if( airLoopHVAC ) {
      IdfObject zoneList(IddObjectType::ZoneList);
      auto zoneListName = modelObject.name().get() + " Control Zones List";
      zoneList.setName(zoneListName);
      m_idfObjects.push_back(zoneList);
      for( const auto & zone : airLoopHVAC->thermalZones() ) {
        auto eg = zoneList.pushExtensibleGroup();
        eg.setString(ZoneListExtensibleFields::ZoneName,zone.name().get());
      }
      idfObject.setString(AvailabilityManager_OptimumStartFields::ZoneListName,zoneListName);
    }
  }

  // MaximumValueforOptimumStartTime
  {
    auto value = modelObject.maximumValueforOptimumStartTime();
    idfObject.setDouble(AvailabilityManager_OptimumStartFields::MaximumValueforOptimumStartTime,value);
  }

  // ControlAlgorithm
  {
    auto value = modelObject.controlAlgorithm();
    idfObject.setString(AvailabilityManager_OptimumStartFields::ControlAlgorithm,value);
  }

  // ConstantTemperatureGradientduringCooling
  {
    auto value = modelObject.constantTemperatureGradientduringCooling();
    idfObject.setDouble(AvailabilityManager_OptimumStartFields::ConstantTemperatureGradientduringCooling,value);
  }

  // ConstantTemperatureGradientduringHeating
  {
    auto value = modelObject.constantTemperatureGradientduringHeating();
    idfObject.setDouble(AvailabilityManager_OptimumStartFields::ConstantTemperatureGradientduringHeating,value);
  }

  // InitialTemperatureGradientduringCooling
  {
    auto value = modelObject.initialTemperatureGradientduringCooling();
    idfObject.setDouble(AvailabilityManager_OptimumStartFields::InitialTemperatureGradientduringCooling,value);
  }

  // InitialTemperatureGradientduringHeating
  {
    auto value = modelObject.initialTemperatureGradientduringHeating();
    idfObject.setDouble(AvailabilityManager_OptimumStartFields::InitialTemperatureGradientduringHeating,value);
  }

  // ConstantStartTime
  {
    auto value = modelObject.constantStartTime();
    idfObject.setDouble(AvailabilityManager_OptimumStartFields::ConstantStartTime,value);
  }

  // NumberofPreviousDays
  {
    auto value = modelObject.numberofPreviousDays();
    idfObject.setUnsigned(AvailabilityManager_OptimumStartFields::NumberofPreviousDays,value);
  }
  
  return idfObject;
}