# open the class to add methods to size all HVAC equipment
class OpenStudio::Model::Model
  # Load the helper libraries for
  require_relative 'Prototype.Fan'
  require_relative 'Prototype.FanConstantVolume'
  require_relative 'Prototype.FanVariableVolume'
  require_relative 'Prototype.FanOnOff'
  require_relative 'Prototype.FanZoneExhaust'
  require_relative 'Prototype.HeatExchangerAirToAirSensibleAndLatent'
  require_relative 'Prototype.ControllerWaterCoil'
  require_relative 'Prototype.Model.hvac'
  require_relative 'Prototype.Model.swh'
  require_relative '../standards/Standards.Model'
  require_relative 'Prototype.building_specific_methods'

  # Creates a DOE prototype building model and replaces
  # the current model with this model.
  #
  # @param building_type [String] the building type
  # @param template [String] the template
  # @param climate_zone [String] the climate zone
  # @param debug [Boolean] If true, will report out more detailed debugging output
  # @return [Bool] returns true if successful, false if not
  # @example Create a Small Office, 90.1-2010, in ASHRAE Climate Zone 5A (Chicago)
  #   model.create_prototype_building('SmallOffice', '90.1-2010', 'ASHRAE 169-2006-5A')

  def create_prototype_building(building_type, template, climate_zone, epw_file, sizing_run_dir = Dir.pwd, debug = false)
    osm_file_increment = 0 
    # There are no reference models for HighriseApartment at vintages Pre-1980 and 1980-2004, nor for NECB 2011. This is a quick check.
    if building_type == 'HighriseApartment'
      if template == 'DOE Ref Pre-1980' || template == 'DOE Ref 1980-2004'
        OpenStudio.logFree(OpenStudio::Error, 'Not available', "DOE Reference models for #{building_type} at template #{template} are not available, the measure is disabled for this specific type.")
        return false
      elsif template == 'NECB 2011'
        OpenStudio.logFree(OpenStudio::Error, 'Not available', "Reference model for #{building_type} at template #{template} is not available, the measure is disabled for this specific type.")
        return false
      end
    end

    lookup_building_type = get_lookup_name(building_type)

    # Retrieve the Prototype Inputs from JSON
    search_criteria = {
      'template' => template,
      'building_type' => building_type
    }

    prototype_input = find_object($os_standards['prototype_inputs'], search_criteria, nil)

    if prototype_input.nil?
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Could not find prototype inputs for #{search_criteria}, cannot create model.")
      return false
    end

    case template
    when 'NECB 2011'



      debug_incremental_changes = false
      load_building_type_methods(building_type, template, climate_zone)
      osm_file_increment += 1
      BTAP::FileIO::save_osm(self,"#{sizing_run_dir}/post_#{osm_file_increment}_load_building_type_methods.osm") if debug_incremental_changes 

      load_geometry(building_type, template, climate_zone)
      osm_file_increment += 1
      BTAP::FileIO::save_osm(self,"#{sizing_run_dir}/post_#{osm_file_increment}_load_geometry.osm")  if debug_incremental_changes 

      getBuilding.setName("#{template}-#{building_type}-#{climate_zone}-#{epw_file} created: #{Time.new}")
      osm_file_increment += 1
      BTAP::FileIO::save_osm(self,"#{sizing_run_dir}/post_#{osm_file_increment}_set_name.osm")  if debug_incremental_changes 

      space_type_map = define_space_type_map(building_type, template, climate_zone)
      File.open("#{sizing_run_dir}/space_type_map.json", 'w') {|f| f.write(JSON.pretty_generate(space_type_map)) }
      
      assign_space_type_stubs('Space Function', template, space_type_map) # TO DO: add support for defining NECB 2011 archetype by building type (versus space function)
      osm_file_increment += 1
      BTAP::FileIO::save_osm(self,"#{sizing_run_dir}/post_#{osm_file_increment}_assign_space_type_stubs.osm")  if debug_incremental_changes 
      
      add_loads(template, climate_zone)
      osm_file_increment += 1
      BTAP::FileIO::save_osm(self,"#{sizing_run_dir}/post_#{osm_file_increment}_add_loads.osm")  if debug_incremental_changes 
      
      apply_infiltration_standard(template)
      osm_file_increment += 1
      BTAP::FileIO::save_osm(self,"#{sizing_run_dir}/post_#{osm_file_increment}_apply_infiltration.osm")  if debug_incremental_changes 
      
      modify_infiltration_coefficients(building_type, template, climate_zone) # does not apply to NECB 2011 but left here for consistency
      osm_file_increment += 1
      BTAP::FileIO::save_osm(self,"#{sizing_run_dir}/post_#{osm_file_increment}_modify_infiltation_coefficients.osm")  if debug_incremental_changes 
      
      modify_surface_convection_algorithm(template)
      osm_file_increment += 1
      BTAP::FileIO::save_osm(self,"#{sizing_run_dir}/post_#{osm_file_increment}_modify_surface_convection_algorithm.osm")  if debug_incremental_changes 
      
      add_constructions(building_type, template, climate_zone)
      osm_file_increment += 1
      BTAP::FileIO::save_osm(self,"#{sizing_run_dir}/post_#{osm_file_increment}_add_constructions.osm")  if debug_incremental_changes 
      
      create_thermal_zones(building_type, template, climate_zone)
      osm_file_increment += 1
      BTAP::FileIO::save_osm(self,"#{sizing_run_dir}/post_#{osm_file_increment}_create_thermal_zones.osm")  if debug_incremental_changes 
      
      add_design_days_and_weather_file(building_type, template, climate_zone, epw_file)
      osm_file_increment += 1
      BTAP::FileIO::save_osm(self,"#{sizing_run_dir}/post_#{osm_file_increment}_add_design_days_and_weather_file.osm")  if debug_incremental_changes 
      
      return false if runSizingRun("#{sizing_run_dir}/SR0") == false
      osm_file_increment += 1
      BTAP::FileIO::save_osm(self,"#{sizing_run_dir}/post_#{osm_file_increment}_sizing_run_0.osm")  if debug_incremental_changes 
      
      add_hvac(building_type, template, climate_zone, prototype_input, epw_file)
      osm_file_increment += 1
      BTAP::FileIO::save_osm(self,"#{sizing_run_dir}/post_#{osm_file_increment}_add_hvac.osm")  if debug_incremental_changes 
      
      osm_file_increment += 1
      add_swh(building_type, template, climate_zone, prototype_input)
      osm_file_increment += 1
      BTAP::FileIO::save_osm(self,"#{sizing_run_dir}/post_#{osm_file_increment}_swh.osm")  if debug_incremental_changes 
      
      apply_sizing_parameters(building_type, template)
      osm_file_increment += 1
      BTAP::FileIO::save_osm(self,"#{sizing_run_dir}/post_#{osm_file_increment}_apply_sizing_paramaters.osm")  if debug_incremental_changes 
      
      yearDescription.get.setDayofWeekforStartDay('Sunday')
      osm_file_increment += 1
      BTAP::FileIO::save_osm(self,"#{sizing_run_dir}/post_#{osm_file_increment}_setDayofWeekforStartDay.osm")  if debug_incremental_changes 
    else

      load_building_type_methods(building_type, template, climate_zone)
      load_geometry(building_type, template, climate_zone)
      getBuilding.setName("#{template}-#{building_type}-#{climate_zone} created: #{Time.new}")
      space_type_map = define_space_type_map(building_type, template, climate_zone)
      assign_space_type_stubs(lookup_building_type, template, space_type_map)
      add_loads(template, climate_zone)
      apply_infiltration_standard(template)
      modify_infiltration_coefficients(building_type, template, climate_zone)
      modify_surface_convection_algorithm(template)
      add_constructions(building_type, template, climate_zone)
      create_thermal_zones(building_type, template, climate_zone)
      add_hvac(building_type, template, climate_zone, prototype_input, epw_file)
      custom_hvac_tweaks(building_type, template, climate_zone, prototype_input, self)
      add_swh(building_type, template, climate_zone, prototype_input)
      custom_swh_tweaks(building_type, template, climate_zone, prototype_input, self)
      add_exterior_lights(building_type, template, climate_zone, prototype_input)
      add_occupancy_sensors(building_type, template, climate_zone)
      add_design_days_and_weather_file(building_type, template, climate_zone, epw_file)
      apply_sizing_parameters(building_type, template)
      yearDescription.get.setDayofWeekforStartDay('Sunday')

    end
    # set climate zone and building type
    getBuilding.setStandardsBuildingType(building_type)
    if climate_zone.include? 'ASHRAE 169-2006-'
      getClimateZones.setClimateZone('ASHRAE', climate_zone.gsub('ASHRAE 169-2006-', ''))
    end

    # For some building types, stories are defined explicitly 
    if building_type == 'SmallHotel'
      building_story_map = PrototypeBuilding::SmallHotel.define_building_story_map(building_type, template, climate_zone)
      assign_building_story(building_type, template, climate_zone, building_story_map)
    end

    # Assign building stories to spaces in the building
    # where stories are not yet assigned.
    assign_spaces_to_stories

    # Perform a sizing run
    if runSizingRun("#{sizing_run_dir}/SR1") == false
      return false
    end

    # If there are any multizone systems, reset damper positions
    # to achieve a 60% ventilation effectiveness minimum for the system
    # following the ventilation rate procedure from 62.1
    apply_multizone_vav_outdoor_air_sizing(template)

    # Apply the prototype HVAC assumptions
    # which include sizing the fan pressure rises based
    # on the flow rate of the system.
    apply_prototype_hvac_assumptions(building_type, template, climate_zone)

    # for 90.1-2010 Outpatient, AHU2 set minimum outdoor air flow rate as 0
    # AHU1 doesn't have economizer
    if building_type == 'Outpatient'
      PrototypeBuilding::Outpatient.modify_oa_controller(template, self)
      # For operating room 1&2 in 2010 and 2013, VAV minimum air flow is set by schedule
      PrototypeBuilding::Outpatient.reset_or_room_vav_minimum_damper(prototype_input, template, self)
    end

    if building_type == 'Hospital'
      PrototypeBuilding::Hospital.modify_hospital_oa_controller(template, self)
    end

    # Apply the HVAC efficiency standard
    apply_hvac_efficiency_standard(template, climate_zone)

    # Add daylighting controls per standard
    # only four zones in large hotel have daylighting controls
    # todo: YXC to merge to the main function
    if building_type == 'LargeHotel'
      PrototypeBuilding::LargeHotel.large_hotel_add_daylighting_controls(template, self)
    elsif building_type == 'Hospital'
      PrototypeBuilding::Hospital.hospital_add_daylighting_controls(template, self)
    else
      add_daylighting_controls(template)
    end

    if building_type == 'QuickServiceRestaurant'
      PrototypeBuilding::QuickServiceRestaurant.update_exhaust_fan_efficiency(template, self)
    elsif building_type == 'FullServiceRestaurant'
      PrototypeBuilding::FullServiceRestaurant.update_exhaust_fan_efficiency(template, self)
    elsif building_type == 'Outpatient'
      PrototypeBuilding::Outpatient.update_exhaust_fan_efficiency(template, self)
    end

    if building_type == 'HighriseApartment'
      PrototypeBuilding::HighriseApartment.update_fan_efficiency(self)
    end

    # Add output variables for debugging
    if debug
      request_timeseries_outputs
    end

    # Finished
    model_status = 'final'
    save(OpenStudio::Path.new("#{sizing_run_dir}/#{model_status}.osm"), true)

    return true
  end

  # Get the name of the building type used in lookups
  #
  # @param building_type [String] the building type
  # @return [String] returns the lookup name as a string
  # @todo Unify the lookup names and eliminate this method
  def get_lookup_name(building_type)
    lookup_name = building_type

    case building_type
    when 'SmallOffice'
      lookup_name = 'Office'
    when 'MediumOffice'
      lookup_name = 'Office'
    when 'LargeOffice'
      lookup_name = 'Office'
    when 'RetailStandalone'
      lookup_name = 'Retail'
    when 'RetailStripmall'
      lookup_name = 'StripMall'
    when 'Office'
      lookup_name = 'Office'
    end

    return lookup_name
  end

  # Loads the library of methods specific to this building type
  #
  # @param building_type [String] the building type
  # @param template [String] the template
  # @param climate_zone [String] the climate zone
  # @return [Bool] returns true if successful, false if not
  def load_building_type_methods(building_type, template, climate_zone)
    building_methods = nil

    case building_type
    when 'SecondarySchool'
      building_methods = 'Prototype.secondary_school'
    when 'PrimarySchool'
      building_methods = 'Prototype.primary_school'
    when 'SmallOffice'
      building_methods = 'Prototype.small_office'
    when 'MediumOffice'
      building_methods = 'Prototype.medium_office'
    when 'LargeOffice'
      building_methods = 'Prototype.large_office'
    when 'SmallHotel'
      building_methods = 'Prototype.small_hotel'
    when 'LargeHotel'
      building_methods = 'Prototype.large_hotel'
    when 'Warehouse'
      building_methods = 'Prototype.warehouse'
    when 'RetailStandalone'
      building_methods = 'Prototype.retail_standalone'
    when 'RetailStripmall'
      building_methods = 'Prototype.retail_stripmall'
    when 'QuickServiceRestaurant'
      building_methods = 'Prototype.quick_service_restaurant'
    when 'FullServiceRestaurant'
      building_methods = 'Prototype.full_service_restaurant'
    when 'Hospital'
      building_methods = 'Prototype.hospital'
    when 'Outpatient'
      building_methods = 'Prototype.outpatient'
    when 'MidriseApartment'
      building_methods = 'Prototype.mid_rise_apartment'
    when 'HighriseApartment'
      building_methods = 'Prototype.high_rise_apartment'
    else
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Building Type = #{building_type} not recognized")
      return false
    end

    require_relative "#{building_methods}"

    return true
  end

  # Loads a geometry-only .osm as a starting point.
  #
  # @param building_type [String] the building type
  # @param template [String] the template
  # @param climate_zone [String] the climate zone
  # @return [Bool] returns true if successful, false if not
  def load_geometry(building_type, template, climate_zone)
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started adding geometry')

    # Determine which geometry file to use
    # based on building_type and template
    # NECB 2011 geometry is not explicitly defined; for NECB 2011 template, latest ASHRAE 90.1 geometry file is assigned (implicitly)

    case building_type
    when 'SecondarySchool'
      geometry_file = if template == 'DOE Ref Pre-1980' || template == 'DOE Ref 1980-2004'
                        'Geometry.secondary_school_pre_1980_to_2004.osm'
                      else
                        'Geometry.secondary_school.osm'
                      end
    when 'PrimarySchool'
      geometry_file = if template == 'DOE Ref Pre-1980' || template == 'DOE Ref 1980-2004'
                        'Geometry.primary_school_pre_1980_to_2004.osm'
                      else
                        'Geometry.primary_school.osm'
                      end
    when 'SmallOffice'
      geometry_file = if template == 'DOE Ref Pre-1980'
                        'Geometry.small_office_pre_1980.osm'
                      else
                        'Geometry.small_office.osm'
                      end
      alt_search_name = 'Office'
    when 'MediumOffice'
      geometry_file = 'Geometry.medium_office.osm'
      alt_search_name = 'Office'
    when 'LargeOffice'
      alt_search_name = 'Office'
      case template
      when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', 'DOE Ref 2004'
        geometry_file = 'Geometry.large_office_reference.osm'
      else
        geometry_file = 'Geometry.large_office_2010.osm'
      end
    when 'SmallHotel'
      case template
      when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004'
        geometry_file = 'Geometry.small_hotel_doe.osm'
      when '90.1-2004'
        geometry_file = 'Geometry.small_hotel_pnnl2004.osm'
      when '90.1-2007'
        geometry_file = 'Geometry.small_hotel_pnnl2007.osm'
      when '90.1-2010'
        geometry_file = 'Geometry.small_hotel_pnnl2010.osm'
      else # '90.1-2013'
        geometry_file = 'Geometry.small_hotel_pnnl2013.osm'
      end
    when 'LargeHotel'
      case template
      when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', 'DOE Ref 2004'
        geometry_file = 'Geometry.large_hotel.doe.osm'
      when '90.1-2007', '90.1-2004'
        geometry_file = 'Geometry.large_hotel.2004_2007.osm'
      when '90.1-2010'
        geometry_file = 'Geometry.large_hotel.2010.osm'
      else
        geometry_file = 'Geometry.large_hotel.2013.osm'
      end
    when 'Warehouse'
      case template
      when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', 'DOE Ref 2004'
        geometry_file = 'Geometry.warehouse_pre_1980_to_2004.osm'
      else
        geometry_file = 'Geometry.warehouse.osm'
      end
    when 'RetailStandalone'
      case template
      when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', 'DOE Ref 2004'
        geometry_file = 'Geometry.retail_standalone.pre1980_post1980.osm'
      when '90.1-2004', '90.1-2007'
        geometry_file = 'Geometry.retail_standalone.2004_2007.osm'
      else # '90.1-2010', '90.1-2013'
        geometry_file = 'Geometry.retail_standalone.2010_2013.osm'
      end
      alt_search_name = 'Retail'
    when 'RetailStripmall'
      geometry_file = 'Geometry.retail_stripmall.osm'
      alt_search_name = 'StripMall'
    when 'QuickServiceRestaurant'
      geometry_file = case template
      when 'DOE Ref Pre-1980'
        'Geometry.quick_service_restaurant_pre1980.osm'
      else # 'DOE Ref 1980-2004','90.1-2010','90.1-2007','90.1-2004','90.1-2013'
        'Geometry.quick_service_restaurant_allothers.osm'
                      end
    when 'FullServiceRestaurant'
      geometry_file = case template
      when 'DOE Ref Pre-1980'
        'Geometry.full_service_restaurant_pre1980.osm'
      else # 'DOE Ref 1980-2004','90.1-2010','90.1-2007','90.1-2004','90.1-2013'
        'Geometry.full_service_restaurant_allothers.osm'
                      end
    when 'Hospital'
      geometry_file = 'Geometry.hospital.osm'
    when 'Outpatient'
      geometry_file = 'Geometry.outpatient.osm'
    when 'MidriseApartment'
      geometry_file = 'Geometry.mid_rise_apartment.osm'
    when 'Office' # For NECB 2011 prototypes (old)
      geometry_file = 'Geometry.large_office_2010.osm'
      alt_search_name = 'Office'
    when 'HighriseApartment'
      geometry_file = 'Geometry.high_rise_apartment.osm'
    else
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Building Type = #{building_type} not recognized")
      return false
    end

    # Load the geometry .osm
    geom_dir = "../../../data/geometry"
    replace_model("#{geom_dir}/#{geometry_file}")

    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished adding geometry')

    return true
  end

  # Replaces all objects in the current model
  # with the objects in the .osm.  Typically used to
  # load a model as a starting point.
  #
  # @param rel_path_to_osm [String] the path to an .osm file, relative to this file
  # @return [Bool] returns true if successful, false if not
  def replace_model(rel_path_to_osm)
    # Take the existing model and remove all the objects
    # (this is cheesy), but need to keep the same memory block
    handles = OpenStudio::UUIDVector.new
    objects.each { |o| handles << o.handle }
    removeObjects(handles)

    model = nil
    if File.dirname(__FILE__)[0] == ':'
      # running from embedded location
    
      # Load geometry from the saved geometry.osm
      geom_model_string = load_resource_relative(rel_path_to_osm)
    
      # version translate from string
      version_translator = OpenStudio::OSVersion::VersionTranslator.new
      model = version_translator.loadModelFromString(geom_model_string)
    else
      abs_path = File.join(File.dirname(__FILE__), rel_path_to_osm)
      
      # version translate from string
      version_translator = OpenStudio::OSVersion::VersionTranslator.new
      model = version_translator.loadModel(abs_path)
    end
    
    if model.empty?
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Version translation failed for #{rel_path_to_osm}")
      return false
    end
    model = model.get

    # Add the objects from the geometry model to the working model
    addObjects(model.toIdfFile.objects)
   
    return true
  end

  # Reads in a mapping between names of space types and
  # names of spaces in the model, creates an empty OpenStudio::Model::SpaceType
  # (no loads, occupants, schedules, etc.) for each space type, and assigns this
  # space type to the list of spaces named.  Later on, these empty space types
  # can be used as keys in a lookup to add loads, schedules, and
  # other inputs that are either typical or governed by a standard.
  #
  # @param building_type [String] the name of the building type
  # @param space_type_map [Hash] a hash where the key is the space type name
  #   and the value is a vector of space names that should be assigned this space type.
  #   The hash for each building is defined inside the Prototype.building_name
  #   e.g. (Prototype.secondary_school.rb) file.
  # @return [Bool] returns true if successful, false if not
  def assign_space_type_stubs(building_type, template, space_type_map)
    space_type_map.each do |space_type_name, space_names|
      # Create a new space type
      stub_space_type = OpenStudio::Model::SpaceType.new(self)
      stub_space_type.setStandardsBuildingType(building_type)
      stub_space_type.setStandardsSpaceType(space_type_name)
      stub_space_type.setName("#{building_type} #{space_type_name}")
      stub_space_type.apply_rendering_color(template)

      space_names.each do |space_name|
        space = getSpaceByName(space_name)
        next if space.empty?
        space = space.get
        space.setSpaceType(stub_space_type)
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Setting #{space.name} to #{building_type}.#{space_type_name}")
      end
    end
    return true
  end

  def add_full_space_type_libs(template)
    space_type_properties_list = find_objects($os_standards['space_types'], 'template' => 'NECB 2011')
    space_type_properties_list.each do |space_type_property|
      stub_space_type = OpenStudio::Model::SpaceType.new(self)
      stub_space_type.setStandardsBuildingType(space_type_property['building_type'])
      stub_space_type.setStandardsSpaceType(space_type_property['space_type'])
      stub_space_type.setName("#{template}-#{space_type_property['building_type']}-#{space_type_property['space_type']}")
      stub_space_type.apply_rendering_color(template)
    end
    add_loads(template)
  end

  def assign_building_story(building_type, template, climate_zone, building_story_map)
    building_story_map.each do |building_story_name, space_names|
      stub_building_story = OpenStudio::Model::BuildingStory.new(self)
      stub_building_story.setName(building_story_name)

      space_names.each do |space_name|
        space = getSpaceByName(space_name)
        next if space.empty?
        space = space.get
        space.setBuildingStory(stub_building_story)
      end
    end
    return true
  end

  # Adds the loads and associated schedules for each space type
  # as defined in the OpenStudio_Standards_space_types.json file.
  # This includes lights, plug loads, occupants, ventilation rate requirements,
  # infiltration, gas equipment (for kitchens, etc.) and typical schedules for each.
  # Some loads are governed by the standard, others are typical values
  # pulled from sources such as the DOE Reference and DOE Prototype Buildings.
  #
  # @param template [String] the template to draw data from
  # @param climate_zone [String] the name of the climate zone the building is in
  # @return [Bool] returns true if successful, false if not

  def add_loads(template, climate_zone = nil)
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying space types (loads)')

    # Loop through all the space types currently in the model,
    # which are placeholders, and give them appropriate loads and schedules
    getSpaceTypes.sort.each do |space_type|
      # Rendering color
      space_type.apply_rendering_color(template)

      # Loads
      space_type.apply_internal_loads(template, true, true, true, true, true, true)

      # Schedules
      space_type.apply_internal_load_schedules(template, true, true, true, true, true, true, true)
    end

    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished applying space types (loads)')

    return true
  end

  # Adds code-minimum constructions based on the building type
  # as defined in the OpenStudio_Standards_construction_sets.json file.
  # Where there is a separate construction set specified for the
  # individual space type, this construction set will be created and applied
  # to this space type, overriding the whole-building construction set.
  #
  # @param building_type [String] the type of building
  # @param template [String] the template to draw data from
  # @param climate_zone [String] the name of the climate zone the building is in
  # @return [Bool] returns true if successful, false if not
  def add_constructions(building_type, template, climate_zone)
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying constructions')
    is_residential = 'No' # default is nonresidential for building level

    # The constructions lookup table uses a slightly different list of
    # building types.
    lookup_building_type = get_lookup_name(building_type)

    # Assign construction to adiabatic construction
    # Assign a material to all internal mass objects
    cp02_carpet_pad = OpenStudio::Model::MasslessOpaqueMaterial.new(self)
    cp02_carpet_pad.setName('CP02 CARPET PAD')
    cp02_carpet_pad.setRoughness('VeryRough')
    cp02_carpet_pad.setThermalResistance(0.21648)
    cp02_carpet_pad.setThermalAbsorptance(0.9)
    cp02_carpet_pad.setSolarAbsorptance(0.7)
    cp02_carpet_pad.setVisibleAbsorptance(0.8)

    normalweight_concrete_floor = OpenStudio::Model::StandardOpaqueMaterial.new(self)
    normalweight_concrete_floor.setName('100mm Normalweight concrete floor')
    normalweight_concrete_floor.setRoughness('MediumSmooth')
    normalweight_concrete_floor.setThickness(0.1016)
    normalweight_concrete_floor.setConductivity(2.31)
    normalweight_concrete_floor.setDensity(2322)
    normalweight_concrete_floor.setSpecificHeat(832)

    nonres_floor_insulation = OpenStudio::Model::MasslessOpaqueMaterial.new(self)
    nonres_floor_insulation.setName('Nonres_Floor_Insulation')
    nonres_floor_insulation.setRoughness('MediumSmooth')
    nonres_floor_insulation.setThermalResistance(2.88291975297193)
    nonres_floor_insulation.setThermalAbsorptance(0.9)
    nonres_floor_insulation.setSolarAbsorptance(0.7)
    nonres_floor_insulation.setVisibleAbsorptance(0.7)

    floor_adiabatic_construction = OpenStudio::Model::Construction.new(self)
    floor_adiabatic_construction.setName('Floor Adiabatic construction')
    floor_layers = OpenStudio::Model::MaterialVector.new
    floor_layers << cp02_carpet_pad
    floor_layers << normalweight_concrete_floor
    floor_layers << nonres_floor_insulation
    floor_adiabatic_construction.setLayers(floor_layers)

    g01_13mm_gypsum_board = OpenStudio::Model::StandardOpaqueMaterial.new(self)
    g01_13mm_gypsum_board.setName('G01 13mm gypsum board')
    g01_13mm_gypsum_board.setRoughness('Smooth')
    g01_13mm_gypsum_board.setThickness(0.0127)
    g01_13mm_gypsum_board.setConductivity(0.1600)
    g01_13mm_gypsum_board.setDensity(800)
    g01_13mm_gypsum_board.setSpecificHeat(1090)
    g01_13mm_gypsum_board.setThermalAbsorptance(0.9)
    g01_13mm_gypsum_board.setSolarAbsorptance(0.7)
    g01_13mm_gypsum_board.setVisibleAbsorptance(0.5)

    wall_adiabatic_construction = OpenStudio::Model::Construction.new(self)
    wall_adiabatic_construction.setName('Wall Adiabatic construction')
    wall_layers = OpenStudio::Model::MaterialVector.new
    wall_layers << g01_13mm_gypsum_board
    wall_layers << g01_13mm_gypsum_board
    wall_adiabatic_construction.setLayers(wall_layers)

    m10_200mm_concrete_block_basement_wall = OpenStudio::Model::StandardOpaqueMaterial.new(self)
    m10_200mm_concrete_block_basement_wall.setName('M10 200mm concrete block basement wall')
    m10_200mm_concrete_block_basement_wall.setRoughness('MediumRough')
    m10_200mm_concrete_block_basement_wall.setThickness(0.2032)
    m10_200mm_concrete_block_basement_wall.setConductivity(1.326)
    m10_200mm_concrete_block_basement_wall.setDensity(1842)
    m10_200mm_concrete_block_basement_wall.setSpecificHeat(912)

    basement_wall_construction = OpenStudio::Model::Construction.new(self)
    basement_wall_construction.setName('Basement Wall construction')
    basement_wall_layers = OpenStudio::Model::MaterialVector.new
    basement_wall_layers << m10_200mm_concrete_block_basement_wall
    basement_wall_construction.setLayers(basement_wall_layers)

    basement_floor_construction = OpenStudio::Model::Construction.new(self)
    basement_floor_construction.setName('Basement Floor construction')
    basement_floor_layers = OpenStudio::Model::MaterialVector.new
    basement_floor_layers << m10_200mm_concrete_block_basement_wall
    basement_floor_layers << cp02_carpet_pad
    basement_floor_construction.setLayers(basement_floor_layers)

    getSurfaces.each do |surface|
      if surface.outsideBoundaryCondition.to_s == 'Adiabatic'
        if surface.surfaceType.to_s == 'Wall'
          surface.setConstruction(wall_adiabatic_construction)
        else
          surface.setConstruction(floor_adiabatic_construction)
        end
      elsif surface.outsideBoundaryCondition.to_s == 'OtherSideCoefficients'
        # Ground
        if surface.surfaceType.to_s == 'Wall'
          surface.setOutsideBoundaryCondition('Ground')
          surface.setConstruction(basement_wall_construction)
        else
          surface.setOutsideBoundaryCondition('Ground')
          surface.setConstruction(basement_floor_construction)
        end
      end
    end

    # Make the default construction set for the building
    bldg_def_const_set = add_construction_set(template, climate_zone, lookup_building_type, nil, is_residential)

    if bldg_def_const_set.is_initialized
      getBuilding.setDefaultConstructionSet(bldg_def_const_set.get)
    else
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'Could not create default construction set for the building.')
      return false
    end

    # Make a construction set for each space type, if one is specified
    getSpaceTypes.each do |space_type|
      # Get the standards building type
      stds_building_type = nil
      if space_type.standardsBuildingType.is_initialized
        stds_building_type = space_type.standardsBuildingType.get
      else
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Space type called '#{space_type.name}' has no standards building type.")
      end

      # Get the standards space type
      stds_spc_type = nil
      if space_type.standardsSpaceType.is_initialized
        stds_spc_type = space_type.standardsSpaceType.get
      else
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Space type called '#{space_type.name}' has no standards space type.")
      end

      # If the standards space type is Attic,
      # the building type should be blank.
      if stds_spc_type == 'Attic'
        stds_building_type = ''
      end

      # Attempt to make a construction set for this space type
      # and assign it if it can be created.
      spc_type_const_set = add_construction_set(template, climate_zone, stds_building_type, stds_spc_type, is_residential)
      if spc_type_const_set.is_initialized
        space_type.setDefaultConstructionSet(spc_type_const_set.get)
      end
    end

    # Add construction from story level, especially for the case when there are residential and nonresidential construction in the same building
    if lookup_building_type == 'SmallHotel'
      getBuildingStorys.each do |story|
        next if story.name.get == 'AtticStory'
        puts "story = #{story.name}"
        is_residential = 'No' # default for building story level
        exterior_spaces_area = 0
        story_exterior_residential_area = 0

        # calculate the propotion of residential area in exterior spaces, see if this story is residential or not
        story.spaces.each do |space|
          next if space.exteriorWallArea.zero?
          space_type = space.spaceType.get
          if space_type.standardsSpaceType.is_initialized
            space_type_name = space_type.standardsSpaceType.get
          end
          data = find_object($os_standards['space_types'], 'template' => template, 'building_type' => lookup_building_type, 'space_type' => space_type_name)
          exterior_spaces_area += space.floorArea
          story_exterior_residential_area += space.floorArea if data['is_residential'] == 'Yes' # "Yes" is residential, "No" or nil is nonresidential
        end
        is_residential = 'Yes' if story_exterior_residential_area / exterior_spaces_area >= 0.5
        next if is_residential == 'No'

        # if the story is identified as residential, assign residential construction set to the spaces on this story.
        building_story_const_set = add_construction_set(template, climate_zone, lookup_building_type, nil, is_residential)
        if building_story_const_set.is_initialized
          story.spaces.each do |space|
            space.setDefaultConstructionSet(building_story_const_set.get)
          end
        end
      end
      # Standars: For whole buildings or floors where 50% or more of the spaces adjacent to exterior walls are used primarily for living and sleeping quarters

    end

    # Make skylights have the same construction as fixed windows
    # sub_surface = self.getBuilding.defaultConstructionSet.get.defaultExteriorSubSurfaceConstructions.get
    # window_construction = sub_surface.fixedWindowConstruction.get
    # sub_surface.setSkylightConstruction(window_construction)

    # Assign a material to all internal mass objects
    material = OpenStudio::Model::StandardOpaqueMaterial.new(self)
    material.setName('Std Wood 6inch')
    material.setRoughness('MediumSmooth')
    material.setThickness(0.15)
    material.setConductivity(0.12)
    material.setDensity(540)
    material.setSpecificHeat(1210)
    material.setThermalAbsorptance(0.9)
    material.setSolarAbsorptance(0.7)
    material.setVisibleAbsorptance(0.7)
    construction = OpenStudio::Model::Construction.new(self)
    construction.setName('InteriorFurnishings')
    layers = OpenStudio::Model::MaterialVector.new
    layers << material
    construction.setLayers(layers)

    # Assign the internal mass construction to existing internal mass objects
    getSpaces.each do |space|
      internal_masses = space.internalMass
      internal_masses.each do |internal_mass|
        internal_mass.internalMassDefinition.setConstruction(construction)
      end
    end

    # get all the space types that are conditioned

    # not required for NECB 2011
    unless template == 'NECB 2011'
      conditioned_space_names = find_conditioned_space_names(building_type, template, climate_zone)
    end

    # add internal mass
    # not required for NECB 2011
    unless (template == 'NECB 2011') ||
           ((building_type == 'SmallHotel') &&
             (template == '90.1-2004' || template == '90.1-2007' || template == '90.1-2010' || template == '90.1-2013'))
      internal_mass_def = OpenStudio::Model::InternalMassDefinition.new(self)
      internal_mass_def.setSurfaceAreaperSpaceFloorArea(2.0)
      internal_mass_def.setConstruction(construction)
      conditioned_space_names.each do |conditioned_space_name|
        space = getSpaceByName(conditioned_space_name)
        if space.is_initialized
          space = space.get
          internal_mass = OpenStudio::Model::InternalMass.new(internal_mass_def)
          internal_mass.setName("#{space.name} Mass")
          internal_mass.setSpace(space)
        end
      end
    end

    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished applying constructions')

    return true
  end

  # Get the list of all conditioned spaces, as defined for each building in the
  # system_to_space_map inside the Prototype.building_name
  # e.g. (Prototype.secondary_school.rb) file.
  #
  # @param (see #add_constructions)
  # @return [Array<String>] returns an array of space names as strings
  def find_conditioned_space_names(building_type, template, climate_zone)
    system_to_space_map = define_hvac_system_map(building_type, template, climate_zone)
    conditioned_space_names = OpenStudio::StringVector.new
    system_to_space_map.each do |system|
      system['space_names'].each do |space_name|
        conditioned_space_names << space_name
      end
    end
    return conditioned_space_names
  end

  # Creates thermal zones to contain each space, as defined for each building in the
  # system_to_space_map inside the Prototype.building_name
  # e.g. (Prototype.secondary_school.rb) file.
  #
  # @param (see #add_constructions)
  # @return [Bool] returns true if successful, false if not
  def create_thermal_zones(building_type, template, climate_zone)
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started creating thermal zones')

    # Remove any Thermal zones assigned
    getThermalZones.each(&:remove)

    # This map define the multipliers for spaces with multipliers not equals to 1
    case building_type
    when 'LargeHotel'
      space_multiplier_map = PrototypeBuilding::LargeHotel.define_space_multiplier
    when 'MidriseApartment'
      space_multiplier_map = PrototypeBuilding::MidriseApartment.define_space_multiplier
    when 'LargeOffice'
      space_multiplier_map = PrototypeBuilding::LargeOffice.define_space_multiplier
    when 'Hospital'
      space_multiplier_map = PrototypeBuilding::Hospital.define_space_multiplier
    else
      space_multiplier_map = {}
    end

    # Create a thermal zone for each space in the self
    getSpaces.each do |space|
      zone = OpenStudio::Model::ThermalZone.new(self)
      zone.setName("#{space.name} ZN")
      unless space_multiplier_map[space.name.to_s].nil?
        zone.setMultiplier(space_multiplier_map[space.name.to_s])
      end
      space.setThermalZone(zone)

      # Skip thermostat for spaces with no space type
      next if space.spaceType.empty?

      # Add a thermostat
      space_type_name = space.spaceType.get.name.get
      thermostat_name = space_type_name + ' Thermostat'
      thermostat = getThermostatSetpointDualSetpointByName(thermostat_name)
      if thermostat.empty?
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Thermostat #{thermostat_name} not found for space name: #{space.name}")
      else
        thermostat_clone = thermostat.get.clone(self).to_ThermostatSetpointDualSetpoint.get
        zone.setThermostatSetpointDualSetpoint(thermostat_clone)
        if template == 'NECB 2011'
          #Set Ideal loads to thermal zone for sizing for NECB needs. We need this for sizing. 
          ideal_loads = OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(self)
          ideal_loads.addToThermalZone(zone)
        end
      end
    end

    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished creating thermal zones')
  end

  # Adds occupancy sensors to certain space types per
  # the PNNL documentation.
  #
  # @param (see #add_constructions)
  # @return [Bool] returns true if successful, false if not
  # @todo genericize and move this method to Standards.Space
  def add_occupancy_sensors(building_type, template, climate_zone)
    # Only add occupancy sensors for 90.1-2010
    case template
    when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007'
      return true
    end

    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started Adding Occupancy Sensors')

    space_type_reduction_map = {
      'SecondarySchool' => { 'Classroom' => 0.32, 'Restroom' => 0.34, 'Office' => 0.22 },
      'PrimarySchool' => { 'Classroom' => 0.32, 'Restroom' => 0.34, 'Office' => 0.22 }
    }

    # Loop through all the space types and reduce lighting operation schedule fractions as-specified
    getSpaceTypes.each do |space_type|
      # Skip space types with no standards building type
      next if space_type.standardsBuildingType.empty?
      stds_bldg_type = space_type.standardsBuildingType.get

      # Skip space types with no standards space type
      next if space_type.standardsSpaceType.empty?
      stds_spc_type = space_type.standardsSpaceType.get

      # Skip building types and space types that aren't listed in the hash
      next unless space_type_reduction_map.key?(stds_bldg_type)
      next unless space_type_reduction_map[stds_bldg_type].key?(stds_spc_type)

      # Get the reduction fraction multiplier
      red_multiplier = 1 - space_type_reduction_map[stds_bldg_type][stds_spc_type]

      lights_sch_names = []
      lights_schs = {}
      reduced_lights_schs = {}

      # Get all of the lights in this space type
      # and determine the list of schedules they use.
      space_type.lights.each do |light|
        # Skip lights that don't have a schedule
        next if light.schedule.empty?
        lights_sch = light.schedule.get
        lights_schs[lights_sch.name.to_s] = lights_sch
        lights_sch_names << lights_sch.name.to_s
      end

      # Loop through the unique list of lighting schedules, cloning
      # and reducing schedule fraction before and after the specified times
      lights_sch_names.uniq.each do |lights_sch_name|
        lights_sch = lights_schs[lights_sch_name]
        # Skip non-ruleset schedules
        next if lights_sch.to_ScheduleRuleset.empty?

        # Clone the schedule (so that we don't mess with lights in
        # other space types that might be using the same schedule).
        new_lights_sch = lights_sch.clone(self).to_ScheduleRuleset.get
        new_lights_sch.setName("#{lights_sch_name} OccSensor Reduction")
        reduced_lights_schs[lights_sch_name] = new_lights_sch

        # Reduce default day schedule
        multiply_schedule(new_lights_sch.defaultDaySchedule, red_multiplier, 0.25)

        # Reduce all other rule schedules
        new_lights_sch.scheduleRules.each do |sch_rule|
          multiply_schedule(sch_rule.daySchedule, red_multiplier, 0.25)
        end
      end # end of lights_sch_names.uniq.each do

      # Loop through all lights instances, replacing old lights
      # schedules with the reduced schedules.
      space_type.lights.each do |light|
        # Skip lights that don't have a schedule
        next if light.schedule.empty?
        old_lights_sch_name = light.schedule.get.name.to_s
        if reduced_lights_schs[old_lights_sch_name]
          light.setSchedule(reduced_lights_schs[old_lights_sch_name])
          OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Occupancy sensor reduction added to '#{light.name}'")
        end
      end
    end

    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished Adding Occupancy Sensors')

    return true
  end # add occupancy sensors

  # Adds exterior lights to the building, as specified
  # in OpenStudio_Standards_prototype_inputs
  #
  # @param (see #add_constructions)
  # @return [Bool] returns true if successful, false if not
  # @todo translate w/linear foot of facade, door, parking, etc
  #   into lookup table and implement that way instead of hard-coding as
  #   inputs in the spreadsheet.
  def add_exterior_lights(building_type, template, climate_zone, prototype_input)
    # TODO: Standards - translate w/linear foot of facade, door, parking, etc
    # into lookup table and implement that way instead of hard-coding as
    # inputs in the spreadsheet.
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started adding exterior lights')

    # Occupancy Sensing Exterior Lights
    # which reduce to 70% power when no one is around.
    unless prototype_input['occ_sensing_exterior_lighting_power'].nil?
      occ_sens_ext_lts_power = prototype_input['occ_sensing_exterior_lighting_power']
      occ_sens_ext_lts_sch_name = prototype_input['occ_sensing_exterior_lighting_schedule']
      occ_sens_ext_lts_name = 'Occ Sensing Exterior Lights'
      occ_sens_ext_lts_def = OpenStudio::Model::ExteriorLightsDefinition.new(self)
      occ_sens_ext_lts_def.setName("#{occ_sens_ext_lts_name} Def")
      occ_sens_ext_lts_def.setDesignLevel(occ_sens_ext_lts_power)
      occ_sens_ext_lts_sch = add_schedule(occ_sens_ext_lts_sch_name)
      occ_sens_ext_lts = OpenStudio::Model::ExteriorLights.new(occ_sens_ext_lts_def, occ_sens_ext_lts_sch)
      occ_sens_ext_lts.setName("#{occ_sens_ext_lts_name} Def")
      occ_sens_ext_lts.setControlOption('AstronomicalClock')
    end

    # Building Facade and Landscape Lights
    # that don't dim at all at night.
    unless prototype_input['nondimming_exterior_lighting_power'].nil?
      nondimming_ext_lts_power = prototype_input['nondimming_exterior_lighting_power']
      nondimming_ext_lts_sch_name = prototype_input['nondimming_exterior_lighting_schedule']
      nondimming_ext_lts_name = 'NonDimming Exterior Lights'
      nondimming_ext_lts_def = OpenStudio::Model::ExteriorLightsDefinition.new(self)
      nondimming_ext_lts_def.setName("#{nondimming_ext_lts_name} Def")
      nondimming_ext_lts_def.setDesignLevel(nondimming_ext_lts_power)
      nondimming_ext_lts_sch = add_schedule(nondimming_ext_lts_sch_name)
      nondimming_ext_lts = OpenStudio::Model::ExteriorLights.new(nondimming_ext_lts_def, nondimming_ext_lts_sch)
      nondimming_ext_lts.setName("#{nondimming_ext_lts_name} Def")
      nondimming_ext_lts.setControlOption('AstronomicalClock')
    end

    # Fuel Equipment, As Exterior:FuelEquipment is not supported by OpenStudio yet,
    # temporarily use Exterior:Lights and set the control option to ScheduleNameOnly
    # todo: change it to Exterior:FuelEquipment when OpenStudio supported it.
    unless prototype_input['exterior_fuel_equipment1_power'].nil?
      fuel_ext_power = prototype_input['exterior_fuel_equipment1_power']
      fuel_ext_sch_name = prototype_input['exterior_fuel_equipment1_schedule']
      fuel_ext_name = 'Fuel equipment 1'
      fuel_ext_def = OpenStudio::Model::ExteriorLightsDefinition.new(self)
      fuel_ext_def.setName("#{fuel_ext_name} Def")
      fuel_ext_def.setDesignLevel(fuel_ext_power)
      fuel_ext_sch = add_schedule(fuel_ext_sch_name)
      fuel_ext_lts = OpenStudio::Model::ExteriorLights.new(fuel_ext_def, fuel_ext_sch)
      fuel_ext_lts.setName(fuel_ext_name.to_s)
      fuel_ext_lts.setControlOption('ScheduleNameOnly')
    end

    unless prototype_input['exterior_fuel_equipment2_power'].nil?
      fuel_ext_power = prototype_input['exterior_fuel_equipment2_power']
      fuel_ext_sch_name = prototype_input['exterior_fuel_equipment2_schedule']
      fuel_ext_name = 'Fuel equipment 2'
      fuel_ext_def = OpenStudio::Model::ExteriorLightsDefinition.new(self)
      fuel_ext_def.setName("#{fuel_ext_name} Def")
      fuel_ext_def.setDesignLevel(fuel_ext_power)
      fuel_ext_sch = add_schedule(fuel_ext_sch_name)
      fuel_ext_lts = OpenStudio::Model::ExteriorLights.new(fuel_ext_def, fuel_ext_sch)
      fuel_ext_lts.setName(fuel_ext_name.to_s)
      fuel_ext_lts.setControlOption('ScheduleNameOnly')
    end

    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished adding exterior lights')

    return true
  end # add exterior lights

  # Changes the infiltration coefficients for the prototype vintages.
  #
  # @param (see #add_constructions)
  # @return [Bool] returns true if successful, false if not
  # @todo Consistency - make prototype and reference vintages consistent
  # @todo Add 90.1-2013?
  def modify_infiltration_coefficients(building_type, template, climate_zone)
    # Select the terrain type, which
    # impacts wind speed, and in turn infiltration
    terrain = 'City'
    case template
    when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013'
      case building_type
      when 'Warehouse'
        terrain = 'Urban'
      when 'SmallHotel'
        terrain = 'Suburbs'
      end
    end
    # Set the terrain type
    getSite.setTerrain(terrain)

    # modify the infiltration coefficients for 90.1-2004, 90.1-2007, 90.1-2010, 90.1-2013
    return true unless template == '90.1-2004' || template == '90.1-2007' || template == '90.1-2010' || template == '90.1-2013' || template == 'NECB 2011'

    # The pre-1980 and 1980-2004 buildings have this:
    # 1.0000,                  !- Constant Term Coefficient
    # 0.0000,                  !- Temperature Term Coefficient
    # 0.0000,                  !- Velocity Term Coefficient
    # 0.0000;                  !- Velocity Squared Term Coefficient
    # The 90.1-2010 buildings have this:
    # 0.0000,                  !- Constant Term Coefficient
    # 0.0000,                  !- Temperature Term Coefficient
    # 0.224,                   !- Velocity Term Coefficient
    # 0.0000;                  !- Velocity Squared Term Coefficient
    getSpaceInfiltrationDesignFlowRates.each do |infiltration|
      infiltration.setConstantTermCoefficient(0.0)
      infiltration.setTemperatureTermCoefficient(0.0)
      infiltration.setVelocityTermCoefficient(0.224)
      infiltration.setVelocitySquaredTermCoefficient(0.0)
    end
  end

  # Sets the inside and outside convection algorithms for different vintages
  #
  # @param (see #add_constructions)
  # @return [Bool] returns true if successful, false if not
  # @todo Consistency - make prototype and reference vintages consistent
  def modify_surface_convection_algorithm(template)
    inside = getInsideSurfaceConvectionAlgorithm
    outside = getOutsideSurfaceConvectionAlgorithm

    case template
    when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004'
      inside.setAlgorithm('TARP')
      outside.setAlgorithm('DOE-2')
    when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013', 'NECB 2011'
      inside.setAlgorithm('TARP')
      outside.setAlgorithm('TARP')
    end
  end

  # Changes the infiltration coefficients for the prototype vintages.
  #
  # @param (see #add_constructions)
  # @return [Bool] returns true if successful, false if not
  # @todo Consistency - make sizing factors consistent
  #   between building types, climate zones, and vintages?
  def apply_sizing_parameters(building_type, template)
    # Default unless otherwise specified
    clg = 1.2
    htg = 1.2
    case template
    when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004'
      case building_type
      when 'PrimarySchool', 'SecondarySchool', 'Outpatient'
        clg = 1.5
        htg = 1.5
      when 'LargeHotel'
        clg = 1.33
        htg = 1.33
      end
    when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013'
      case building_type
      when 'Hospital', 'LargeHotel', 'MediumOffice', 'LargeOffice', 'Outpatient', 'PrimarySchool'
        clg = 1.0
        htg = 1.0
      end
    when 'NECB 2011'
      clg = 1.3
      htg = 1.3
    end

    sizing_params = getSizingParameters
    sizing_params.setHeatingSizingFactor(htg)
    sizing_params.setCoolingSizingFactor(clg)

    OpenStudio.logFree(OpenStudio::Info, 'openstudio.prototype.Model', "Set sizing factors to #{htg} for heating and #{clg} for cooling.")
  end

  def apply_prototype_hvac_assumptions(building_type, template, climate_zone)
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying prototype HVAC assumptions.')

    ##### Apply equipment efficiencies

    # Fans
    # Pressure Rise
    getFanConstantVolumes.sort.each { |obj| obj.apply_prototype_fan_pressure_rise(building_type, template, climate_zone) }
    getFanVariableVolumes.sort.each { |obj| obj.apply_prototype_fan_pressure_rise(building_type, template, climate_zone) }
    getFanOnOffs.sort.each { |obj| obj.apply_prototype_fan_pressure_rise(building_type, template, climate_zone) }
    getFanZoneExhausts.sort.each(&:apply_prototype_fan_pressure_rise)

    # Motor Efficiency
    getFanConstantVolumes.sort.each { |obj| obj.apply_prototype_fan_efficiency(template) }
    getFanVariableVolumes.sort.each { |obj| obj.apply_prototype_fan_efficiency(template) }
    getFanOnOffs.sort.each { |obj| obj.apply_prototype_fan_efficiency(template) }
    getFanZoneExhausts.sort.each { |obj| obj.apply_prototype_fan_efficiency(template) }

    ##### Add Economizers

    if template != 'NECB 2011'
      # Create an economizer maximum OA fraction of 70%
      # to reflect damper leakage per PNNL
      econ_max_70_pct_oa_sch = OpenStudio::Model::ScheduleRuleset.new(self)
      econ_max_70_pct_oa_sch.setName('Economizer Max OA Fraction 70 pct')
      econ_max_70_pct_oa_sch.defaultDaySchedule.setName('Economizer Max OA Fraction 70 pct Default')
      econ_max_70_pct_oa_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 0.7)
    else
      # NECB 2011 prescribes ability to provide 100% OA (5.2.2.7-5.2.2.9)
      econ_max_100_pct_oa_sch = OpenStudio::Model::ScheduleRuleset.new(self)
      econ_max_100_pct_oa_sch.setName('Economizer Max OA Fraction 100 pct')
      econ_max_100_pct_oa_sch.defaultDaySchedule.setName('Economizer Max OA Fraction 100 pct Default')
      econ_max_100_pct_oa_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 1.0)
    end

    # Check each airloop
    getAirLoopHVACs.each do |air_loop|
      if air_loop.economizer_required?(template, climate_zone) == true
        # If an economizer is required, determine the economizer type
        # in the prototype buildings, which depends on climate zone.
        economizer_type = nil
        case template
        when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007'
          economizer_type = 'DifferentialDryBulb'
        when '90.1-2010', '90.1-2013'
          case climate_zone
          when 'ASHRAE 169-2006-1A',
              'ASHRAE 169-2006-2A',
              'ASHRAE 169-2006-3A',
              'ASHRAE 169-2006-4A'
            economizer_type = 'DifferentialEnthalpy'
          else
            economizer_type = 'DifferentialDryBulb'
          end
        when 'NECB 2011'
          # NECB 5.2.2.8 states that economizer can be controlled based on difference betweeen
          # return air temperature and outside air temperature OR return air enthalpy
          # and outside air enthalphy; latter chosen to be consistent with MNECB and CAN-QUEST implementation
          economizer_type = 'DifferentialEnthalpy'
        end

        # Set the economizer type
        # Get the OA system and OA controller
        oa_sys = air_loop.airLoopHVACOutdoorAirSystem
        if oa_sys.is_initialized
          oa_sys = oa_sys.get
        else
          OpenStudio.logFree(OpenStudio::Error, 'openstudio.prototype.Model', "#{air_loop.name} is required to have an economizer, but it has no OA system.")
          next
        end
        oa_control = oa_sys.getControllerOutdoorAir
        oa_control.setEconomizerControlType(economizer_type)
        if template != 'NECB 2011'
          # oa_control.setMaximumFractionofOutdoorAirSchedule(econ_max_70_pct_oa_sch)
        end

        # Check that the economizer type set by the prototypes
        # is not prohibited by code.  If it is, change to no economizer.
        unless air_loop.economizer_type_allowable?(template, climate_zone)
          OpenStudio.logFree(OpenStudio::Warn, 'openstudio.prototype.Model', "#{air_loop.name} is required to have an economizer, but the type chosen, #{economizer_type} is prohibited by code for #{template}, climate zone #{climate_zone}.  Economizer type will be switched to No Economizer.")
          oa_control.setEconomizerControlType('NoEconomizer')
        end

      end
    end

    # TODO: What is the logic behind hard-sizing
    # hot water coil convergence tolerances?
    getControllerWaterCoils.sort.each(&:set_convergence_limits)

    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished applying prototype HVAC assumptions.')
  end

  def add_debugging_variables(type)
    # 'detailed'
    # 'timestep'
    # 'hourly'
    # 'daily'
    # 'monthly'

    vars = []
    case type
    when 'service_water_heating'
      var_names << ['Water Heater Water Volume Flow Rate', 'timestep']
      var_names << ['Water Use Equipment Hot Water Volume Flow Rate', 'timestep']
      var_names << ['Water Use Equipment Cold Water Volume Flow Rate', 'timestep']
      var_names << ['Water Use Equipment Hot Water Temperature', 'timestep']
      var_names << ['Water Use Equipment Cold Water Temperature', 'timestep']
      var_names << ['Water Use Equipment Mains Water Volume', 'timestep']
      var_names << ['Water Use Equipment Target Water Temperature', 'timestep']
      var_names << ['Water Use Equipment Mixed Water Temperature', 'timestep']
      var_names << ['Water Heater Tank Temperature', 'timestep']
      var_names << ['Water Heater Use Side Mass Flow Rate', 'timestep']
      var_names << ['Water Heater Heating Rate', 'timestep']
      var_names << ['Water Heater Water Volume Flow Rate', 'timestep']
      var_names << ['Water Heater Water Volume', 'timestep']
    end

    var_names.each do |var_name, reporting_frequency|
      output_var = OpenStudio::Model::OutputVariable.new(var_name, self)
      output_var.setReportingFrequency(reporting_frequency)
    end
  end

  def run(run_dir = "#{Dir.pwd}/Run")
    # If the run directory is not specified
    # run in the current working directory

    # Make the directory if it doesn't exist
    unless Dir.exist?(run_dir)
      Dir.mkdir(run_dir)
    end

    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Started simulation in '#{run_dir}'")

    # Change the simulation to only run the weather file
    # and not run the sizing day simulations
    sim_control = getSimulationControl
    sim_control.setRunSimulationforSizingPeriods(false)
    sim_control.setRunSimulationforWeatherFileRunPeriods(true)

    # Save the model to energyplus idf
    idf_name = 'in.idf'
    osm_name = 'in.osm'
    forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new
    idf = forward_translator.translateModel(self)
    idf_path = OpenStudio::Path.new("#{run_dir}/#{idf_name}")
    osm_path = OpenStudio::Path.new("#{run_dir}/#{osm_name}")
    idf.save(idf_path, true)
    save(osm_path, true)

    # Set up the sizing simulation
    # Find the weather file
    epw_path = nil
    if weatherFile.is_initialized
      epw_path = weatherFile.get.path
      if epw_path.is_initialized
        if File.exist?(epw_path.get.to_s)
          epw_path = epw_path.get
        else
          # If this is an always-run Measure, need to check a different path
          alt_weath_path = File.expand_path(File.join(File.dirname(__FILE__), '../../../resources'))
          alt_epw_path = File.expand_path(File.join(alt_weath_path, epw_path.get.to_s))
          if File.exist?(alt_epw_path)
            epw_path = OpenStudio::Path.new(alt_epw_path)
          else
            OpenStudio.logFree(OpenStudio::Error, 'openstudio.prototype.Model', "Model has been assigned a weather file, but the file is not in the specified location of '#{epw_path.get}'.")
            return false
          end
        end
      else
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.prototype.Model', 'Model has a weather file assigned, but the weather file path has been deleted.')
        return false
      end
    else
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.prototype.Model', 'Model has not been assigned a weather file.3')
      return false
    end

    # If running on a regular desktop, use RunManager.
    # If running on OpenStudio Server, use WorkFlowMananger
    # to avoid slowdown from the sizing run.
    use_runmanager = true

    begin
      require 'openstudio-workflow'
      use_runmanager = false
    rescue LoadError
      use_runmanager = true
    end

    sql_path = nil
    if use_runmanager == true
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.prototype.Model', 'Running sizing run with RunManager.')

      # Find EnergyPlus
      ep_dir = OpenStudio.getEnergyPlusDirectory
      ep_path = OpenStudio.getEnergyPlusExecutable
      ep_tool = OpenStudio::Runmanager::ToolInfo.new(ep_path)
      idd_path = OpenStudio::Path.new(ep_dir.to_s + '/Energy+.idd')
      output_path = OpenStudio::Path.new("#{run_dir}/")

      # Make a run manager and queue up the sizing run
      run_manager_db_path = OpenStudio::Path.new("#{run_dir}/run.db")
      run_manager = OpenStudio::Runmanager::RunManager.new(run_manager_db_path, true, false, false, false)
      job = OpenStudio::Runmanager::JobFactory.createEnergyPlusJob(ep_tool,
                                                                   idd_path,
                                                                   idf_path,
                                                                   epw_path,
                                                                   output_path)

      run_manager.enqueue(job, true)

      # Start the sizing run and wait for it to finish.
      while run_manager.workPending
        sleep 1
        OpenStudio::Application.instance.processEvents
      end

      sql_path = OpenStudio::Path.new("#{run_dir}/Energyplus/eplusout.sql")

      OpenStudio.logFree(OpenStudio::Info, 'openstudio.prototype.Model', "Finished sizing run in #{(Time.new - start_time).round}sec.")

    else # Use the openstudio-workflow gem
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.prototype.Model', 'Running sizing run with openstudio-workflow gem.')

      # Copy the weather file to this directory
      FileUtils.copy(epw_path.to_s, run_dir)

      # Run the simulation
      sim = OpenStudio::Workflow.run_energyplus('Local', run_dir)
      final_state = sim.run

      if final_state == :finished
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.prototype.Model', "Finished sizing run in #{(Time.new - start_time).round}sec.")
      end

      sql_path = OpenStudio::Path.new("#{run_dir}/run/eplusout.sql")

    end

    # Load the sql file created by the sizing run
    sql_path = OpenStudio::Path.new("#{run_dir}/Energyplus/eplusout.sql")
    if OpenStudio.exists(sql_path)
      sql = OpenStudio::SqlFile.new(sql_path)
      # Check to make sure the sql file is readable,
      # which won't be true if EnergyPlus crashed during simulation.
      unless sql.connectionOpen
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "The run failed.  Look at the eplusout.err file in #{File.dirname(sql_path.to_s)} to see the cause.")
        return false
      end
      # Attach the sql file from the run to the sizing model
      setSqlFile(sql)
    else
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Results for the sizing run couldn't be found here: #{sql_path}.")
      return false
    end

    # Check that the run finished without severe errors
    error_query = "SELECT ErrorMessage
        FROM Errors
        WHERE ErrorType='1'"

    errs = sqlFile.get.execAndReturnVectorOfString(error_query)
    if errs.is_initialized
      errs = errs.get
      unless errs.empty?
        errs = errs.get
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "The run failed with the following severe errors: #{errs.join('\n')}.")
        return false
      end
    end

    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Finished simulation in '#{run_dir}'")

    return true
  end

  def request_timeseries_outputs
    # "detailed"
    # "timestep"
    # "hourly"
    # "daily"
    # "monthly"

    vars = []
    # vars << ['Heating Coil Gas Rate', 'detailed']
    # vars << ['Zone Thermostat Air Temperature', 'detailed']
    # vars << ['Zone Thermostat Heating Setpoint Temperature', 'detailed']
    # vars << ['Zone Thermostat Cooling Setpoint Temperature', 'detailed']
    # vars << ['Zone Air System Sensible Heating Rate', 'detailed']
    # vars << ['Zone Air System Sensible Cooling Rate', 'detailed']
    # vars << ['Fan Electric Power', 'detailed']
    # vars << ['Zone Mechanical Ventilation Standard Density Volume Flow Rate', 'detailed']
    # vars << ['Air System Outdoor Air Mass Flow Rate', 'detailed']
    # vars << ['Air System Outdoor Air Flow Fraction', 'detailed']
    # vars << ['Air System Outdoor Air Minimum Flow Fraction', 'detailed']

    # vars << ['Water Use Equipment Hot Water Volume Flow Rate', 'hourly']
    # vars << ['Water Use Equipment Cold Water Volume Flow Rate', 'hourly']
    # vars << ['Water Use Equipment Total Volume Flow Rate', 'hourly']
    # vars << ['Water Use Equipment Hot Water Temperature', 'hourly']
    # vars << ['Water Use Equipment Cold Water Temperature', 'hourly']
    # vars << ['Water Use Equipment Target Water Temperature', 'hourly']
    # vars << ['Water Use Equipment Mixed Water Temperature', 'hourly']

    # vars << ['Water Use Connections Hot Water Volume Flow Rate', 'hourly']
    # vars << ['Water Use Connections Cold Water Volume Flow Rate', 'hourly']
    # vars << ['Water Use Connections Total Volume Flow Rate', 'hourly']
    # vars << ['Water Use Connections Hot Water Temperature', 'hourly']
    # vars << ['Water Use Connections Cold Water Temperature', 'hourly']
    # vars << ['Water Use Connections Plant Hot Water Energy', 'hourly']
    # vars << ['Water Use Connections Return Water Temperature', 'hourly']

    # vars << ['Air System Outdoor Air Economizer Status','timestep']
    # vars << ['Air System Outdoor Air Heat Recovery Bypass Status','timestep']
    # vars << ['Air System Outdoor Air High Humidity Control Status','timestep']
    # vars << ['Air System Outdoor Air Flow Fraction','timestep']
    # vars << ['Air System Outdoor Air Minimum Flow Fraction','timestep']
    # vars << ['Air System Outdoor Air Mass Flow Rate','timestep']
    # vars << ['Air System Mixed Air Mass Flow Rate','timestep']

    # vars << ['Heating Coil Gas Rate','timestep']
    vars << ['Boiler Part Load Ratio', 'timestep']
    vars << ['Boiler Gas Rate', 'timestep']
    # vars << ['Boiler Gas Rate','timestep']
    # vars << ['Fan Electric Power','timestep']

    vars << ['Pump Electric Power', 'timestep']
    vars << ['Pump Outlet Temperature', 'timestep']
    vars << ['Pump Mass Flow Rate', 'timestep']

    # vars << ['Zone Air Terminal VAV Damper Position','timestep']
    # vars << ['Zone Air Terminal Minimum Air Flow Fraction','timestep']
    # vars << ['Zone Air Terminal Outdoor Air Volume Flow Rate','timestep']
    # vars << ['Zone Lights Electric Power','hourly']
    # vars << ['Daylighting Lighting Power Multiplier','hourly']
    # vars << ['Schedule Value','hourly']

    vars.each do |var, freq|
      output_var = OpenStudio::Model::OutputVariable.new(var, self)
      output_var.setReportingFrequency(freq)
    end
  end

  def clear_and_set_example_constructions
    # Define Materials
    name = 'opaque material'
    thickness = 0.012700
    conductivity = 0.160000
    opaque_mat = BTAP::Resources::Envelope::Materials::Opaque.create_opaque_material(self, name, thickness, conductivity)

    name = 'insulation material'
    thickness = 0.050000
    conductivity = 0.043000
    insulation_mat = BTAP::Resources::Envelope::Materials::Opaque.create_opaque_material(self, name, thickness, conductivity)

    name = 'simple glazing test'
    shgc = 0.250000
    ufactor = 3.236460
    thickness = 0.003000
    visible_transmittance = 0.160000
    simple_glazing_mat = BTAP::Resources::Envelope::Materials::Fenestration.create_simple_glazing(self, name, shgc, ufactor, thickness, visible_transmittance)

    name = 'Standard Glazing Test'
    thickness = 0.003
    conductivity = 0.9
    solar_trans_normal = 0.84
    front_solar_ref_normal = 0.075
    back_solar_ref_normal = 0.075
    vlt = 0.9
    front_vis_ref_normal = 0.081
    back_vis_ref_normal = 0.081
    ir_trans_normal = 0.0
    front_ir_emis = 0.84
    back_ir_emis = 0.84
    optical_data_type = 'SpectralAverage'
    dirt_correction_factor = 1.0
    is_solar_diffusing = false

    standard_glazing_mat = BTAP::Resources::Envelope::Materials::Fenestration.create_standard_glazing(self,
                                                                                                      name,
                                                                                                      thickness,
                                                                                                      conductivity,
                                                                                                      solar_trans_normal,
                                                                                                      front_solar_ref_normal,
                                                                                                      back_solar_ref_normal, vlt,
                                                                                                      front_vis_ref_normal,
                                                                                                      back_vis_ref_normal,
                                                                                                      ir_trans_normal,
                                                                                                      front_ir_emis,
                                                                                                      back_ir_emis,
                                                                                                      optical_data_type,
                                                                                                      dirt_correction_factor,
                                                                                                      is_solar_diffusing)

    # Define Constructions
    # # Surfaces
    ext_wall                            = BTAP::Resources::Envelope::Constructions.create_construction(self, 'OpaqueConstructionExtWall',                    [opaque_mat, insulation_mat], insulation_mat)
    ext_roof                            = BTAP::Resources::Envelope::Constructions.create_construction(self, 'OpaqueConstructionExtRoof',                    [opaque_mat, insulation_mat], insulation_mat)
    ext_floor                           = BTAP::Resources::Envelope::Constructions.create_construction(self, 'OpaqueConstructionExtFloor',                   [opaque_mat, insulation_mat], insulation_mat)
    grnd_wall                           = BTAP::Resources::Envelope::Constructions.create_construction(self, 'OpaqueConstructionGrndWall',                   [opaque_mat, insulation_mat], insulation_mat)
    grnd_roof                           = BTAP::Resources::Envelope::Constructions.create_construction(self, 'OpaqueConstructionGrndRoof',                   [opaque_mat, insulation_mat], insulation_mat)
    grnd_floor                          = BTAP::Resources::Envelope::Constructions.create_construction(self, 'OpaqueConstructionGrndFloor',                  [opaque_mat, insulation_mat], insulation_mat)
    int_wall                            = BTAP::Resources::Envelope::Constructions.create_construction(self, 'OpaqueConstructionIntWall',                    [opaque_mat, insulation_mat], insulation_mat)
    int_roof                            = BTAP::Resources::Envelope::Constructions.create_construction(self, 'OpaqueConstructionIntRoof',                    [opaque_mat, insulation_mat], insulation_mat)
    int_floor                           = BTAP::Resources::Envelope::Constructions.create_construction(self, 'OpaqueConstructionIntFloor',                   [opaque_mat, insulation_mat], insulation_mat)
    # # Subsurfaces
    fixed_window                        = BTAP::Resources::Envelope::Constructions.create_construction(self, 'FenestrationConstructionFixed',                [simple_glazing_mat])
    operable_window                     = BTAP::Resources::Envelope::Constructions.create_construction(self, 'FenestrationConstructionOperable',             [simple_glazing_mat])
    glass_door                          = BTAP::Resources::Envelope::Constructions.create_construction(self, 'FenestrationConstructionDoor',                 [standard_glazing_mat])
    door                                = BTAP::Resources::Envelope::Constructions.create_construction(self, 'OpaqueConstructionDoor',                       [opaque_mat, insulation_mat], insulation_mat)
    overhead_door                       = BTAP::Resources::Envelope::Constructions.create_construction(self, 'OpaqueConstructionOverheadDoor',               [opaque_mat, insulation_mat], insulation_mat)
    skylt                               = BTAP::Resources::Envelope::Constructions.create_construction(self, 'FenestrationConstructionSkylight',             [standard_glazing_mat])
    daylt_dome                          = BTAP::Resources::Envelope::Constructions.create_construction(self, 'FenestrationConstructionDomeConstruction',     [standard_glazing_mat])
    daylt_diffuser                      = BTAP::Resources::Envelope::Constructions.create_construction(self, 'FenestrationConstructionDiffuserConstruction', [standard_glazing_mat])

    # Define Construction Sets
    # # Surface
    exterior_construction_set = BTAP::Resources::Envelope::ConstructionSets.create_default_surface_constructions(self, 'ExteriorSet', ext_wall, ext_roof, ext_floor)
    interior_construction_set = BTAP::Resources::Envelope::ConstructionSets.create_default_surface_constructions(self, 'InteriorSet', int_wall, int_roof, int_floor)
    ground_construction_set   = BTAP::Resources::Envelope::ConstructionSets.create_default_surface_constructions(self, 'GroundSet', grnd_wall, grnd_roof, grnd_floor)

    # # Subsurface
    subsurface_exterior_construction_set = BTAP::Resources::Envelope::ConstructionSets.create_subsurface_construction_set(self, fixed_window, operable_window, door, glass_door, overhead_door, skylt, daylt_dome, daylt_diffuser)
    subsurface_interior_construction_set = BTAP::Resources::Envelope::ConstructionSets.create_subsurface_construction_set(self, fixed_window, operable_window, door, glass_door, overhead_door, skylt, daylt_dome, daylt_diffuser)

    # Define default construction sets.
    name = 'Construction Set 1'
    default_construction_set = BTAP::Resources::Envelope::ConstructionSets.create_default_construction_set(self, name, exterior_construction_set, interior_construction_set, ground_construction_set, subsurface_exterior_construction_set, subsurface_interior_construction_set)

    # Assign default to the model.
    getBuilding.setDefaultConstructionSet(default_construction_set)

    return default_construction_set
  end

  private

  # Method to multiply the values in a day schedule by a specified value
  # but only when the existing value is higher than a specified lower limit.
  # This limit prevents occupancy sensors from affecting unoccupied hours.
  def multiply_schedule(day_sch, multiplier, limit)
    # Record the original times and values
    times = day_sch.times
    values = day_sch.values

    # Remove the original times and values
    day_sch.clearValues

    # Create new values by using the multiplier on the original values
    new_values = []
    values.each do |value|
      new_values << if value > limit
                      value * multiplier
                    else
                      value
                    end
    end

    # Add the revised time/value pairs to the schedule
    new_values.each_with_index do |new_value, i|
      day_sch.addValue(times[i], new_value)
    end
  end # end reduce schedule
end