# *********************************************************************
# *  Copyright (c) 2008-2015, Natural Resources Canada
# *  All rights reserved.
# *
# *  This library is free software; you can redistribute it and/or
# *  modify it under the terms of the GNU Lesser General Public
# *  License as published by the Free Software Foundation; either
# *  version 2.1 of the License, or (at your option) any later version.
# *
# *  This library is distributed in the hope that it will be useful,
# *  but WITHOUT ANY WARRANTY; without even the implied warranty of
# *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# *  Lesser General Public License for more details.
# *
# *  You should have received a copy of the GNU Lesser General Public
# *  License along with this library; if not, write to the Free Software
# *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
# **********************************************************************/

require 'json'
require "#{File.dirname(__FILE__)}/btap"


module BTAP
  #Contains data and methods for compliance and archetype work.
  module Compliance
    # Contains NECB relelvant methods and data.
    module NECB2011
      # NECB data tables / arrays.
      module Data
        #Envelope Conductance values for each climat zone / HDD limits.
        module Conductances
          #array of conductances(metric) per climate zone.
          Wall = [0.315, 0.278, 0.247, 0.210, 0.210, 0.183]
          Roof = [0.227, 0.183, 0.183, 0.162, 0.162, 0.142]
          Floor = [0.227, 0.183, 0.183, 0.162, 0.162, 0.142]
          Window = [2.400, 2.200, 2.200, 2.200, 2.200, 1.600]
          Door = [2.400, 2.200, 2.200, 2.200, 2.200, 1.600]
          GroundWall = [0.568, 0.379, 0.284, 0.284, 0.284, 0.210]
          GroundRoof = [0.568, 0.379, 0.284, 0.284, 0.284, 0.210]
          GroundFloor = [0.757, 0.757, 0.757, 0.757, 0.757, 0.379]
        end
      end


      #This method ???.
      #@author phylroy.lopez@nrcan.gc.ca
      #@param hdd [Float]
      #@return [Double] a constant float
      def self.max_fwdr(hdd)
        #NECB 3.2.1.4

        if hdd < 4000
          return 0.40
        elsif hdd >= 4000 and hdd <=7000
          return (2000-0.2 * hdd)/3000
        elsif hdd >7000
          return 0.20
        end
      end


      # This method will set the the envelope (wall, roof, glazings) to  values to
      # the default NECB2011 values based on the heating degree day value (hdd) surface by surface.
      #@author phylroy.lopez@nrcan.gc.ca
      #@param model [OpenStudio::model::Model] A model object
      #@param hdd [Float]
      def self.set_necb_envelope(model, runner=nil)

        BTAP::runner_register("Info", "set_envelope_surfaces_to_necb!", runner)
        if model.weatherFile.empty? or model.weatherFile.get.path.empty? or not File.exists?(model.weatherFile.get.path.get.to_s)

          BTAP::runner_register("Error", "Weather file is not defined. Please ensure the weather file is defined and exists.", runner)
          return false
        end
        hdd = BTAP::Environment::WeatherFile.new(model.weatherFile.get.path.get).hdd18

        #interate Through all surfaces
        model.getSurfaces.sort.each do |surface|
          #set fenestration to wall ratio.
          BTAP::Compliance::NECB2011::set_necb_external_surface_conductance(surface, hdd, false, 1.0)

          #dig into the subsurface and change them as well.
          model.getSubSurfaces.sort.each do |subsurface|
            BTAP::Compliance::NECB2011::set_necb_external_subsurface_conductance(subsurface, hdd)
          end
        end
      end

      # this will create a copy and convert all construction sets to NECB reference conductances.
      #@author phylroy.lopez@nrcan.gc.ca
      #@param model [OpenStudio::model::Model] A model object
      #@param default_surface_construction_set [String]
      #@return [Boolean] returns true if sucessful, false if not
      def self.set_construction_set_to_necb!(model, default_surface_construction_set,
          runner = nil,
          scale_wall = 1.0,
          scale_floor = 1.0,
          scale_roof = 1.0,
          scale_ground_wall = 1.0,
          scale_ground_floor = 1.0,
          scale_ground_roof = 1.0,
          scale_door = 1.0,
          scale_window = 1.0
      )
        BTAP::runner_register("Info", "set_construction_set_to_necb!", runner)
        if model.weatherFile.empty? or model.weatherFile.get.path.empty? or not File.exists?(model.weatherFile.get.path.get.to_s)

          BTAP::runner_register("Error", "Weather file is not defined. Please ensure the weather file is defined and exists.", runner)
          return false
        end
        hdd = BTAP::Environment::WeatherFile.new(model.weatherFile.get.path.get).hdd18

        old_name = default_surface_construction_set.name.get.to_s


        climate_zone_index = get_climate_zone_index(hdd)
        new_name = "#{old_name} at hdd = #{hdd}"

        #convert conductance values to rsi values. (Note: we should really be only using conductances in)
        wall_rsi = 1.0 / (scale_wall * BTAP::Compliance::NECB2011::Data::Conductances::Wall[climate_zone_index])
        floor_rsi = 1.0 / (scale_floor * BTAP::Compliance::NECB2011::Data::Conductances::Floor[climate_zone_index])
        roof_rsi = 1.0 / (scale_roof * BTAP::Compliance::NECB2011::Data::Conductances::Roof[climate_zone_index])
        ground_wall_rsi = 1.0 / (scale_ground_wall * BTAP::Compliance::NECB2011::Data::Conductances::GroundWall[climate_zone_index])
        ground_floor_rsi = 1.0 / (scale_ground_floor * BTAP::Compliance::NECB2011::Data::Conductances::GroundFloor[climate_zone_index])
        ground_roof_rsi = 1.0 / (scale_ground_roof * BTAP::Compliance::NECB2011::Data::Conductances::GroundRoof[climate_zone_index])
        door_rsi = 1.0 / (scale_door * BTAP::Compliance::NECB2011::Data::Conductances::Door[climate_zone_index])
        window_rsi = 1.0 / (scale_window * BTAP::Compliance::NECB2011::Data::Conductances::Window[climate_zone_index])
        BTAP::Resources::Envelope::ConstructionSets::customize_default_surface_construction_set_rsi!(model, new_name, default_surface_construction_set,
                                                                                                     wall_rsi, floor_rsi, roof_rsi,
                                                                                                     ground_wall_rsi, ground_floor_rsi, ground_roof_rsi,
                                                                                                     window_rsi, nil, nil,
                                                                                                     window_rsi, nil, nil,
                                                                                                     door_rsi,
                                                                                                     door_rsi, nil, nil,
                                                                                                     door_rsi,
                                                                                                     window_rsi, nil, nil,
                                                                                                     window_rsi, nil, nil,
                                                                                                     window_rsi, nil, nil
        )
        BTAP::runner_register("Info", "set_construction_set_to_necb! was sucessful.", runner)
        return true
      end

      # This method will convert in place(over write) a construction set to necb conductances.
      #@author phylroy.lopez@nrcan.gc.ca
      #@param model [OpenStudio::model::Model] A model object
      #@param scale_wall [Float]
      #@param scale_floor [Float]
      #@param scale_roof [Float]
      #@param scale_ground_wall [Float]
      #@param scale_ground_floor [Float]
      #@param scale_ground_roof [Float]
      #@param scale_door [Float]
      #@param scale_window [Float]
      def self.set_all_construction_sets_to_necb!(model,
          runner = nil,
          scale_wall = 1.0,
          scale_floor = 1.0,
          scale_roof = 1.0,
          scale_ground_wall = 1.0,
          scale_ground_floor = 1.0,
          scale_ground_roof = 1.0,
          scale_door = 1.0,
          scale_window = 1.0)

        model.getDefaultConstructionSets.sort.each do |set|
          self.set_construction_set_to_necb!(model,
                                             set,
                                             runner,
                                             scale_wall,
                                             scale_floor,
                                             scale_roof,
                                             scale_ground_wall,
                                             scale_ground_floor,
                                             scale_ground_roof,
                                             scale_door,
                                             scale_window)
        end
        #sets all surfaces to use default constructions sets except adiabatic, where it does a hard assignment of the interior wall construction type. 
        model.getPlanarSurfaces.sort.each {|item| item.resetConstruction}
        #if the default construction set is defined..try to assign the interior wall to the adiabatic surfaces
        BTAP::Resources::Envelope::assign_interior_surface_construction_to_adiabatic_surfaces(model, nil)
      end

      #This model gets the climate zone column index from tables 3.2.2.x
      #@author phylroy.lopez@nrcan.gc.ca
      #@param hdd [Float]
      #@return [Fixnum] climate zone 4-8
      def self.get_climate_zone_index(hdd)
        #check for climate zone index from NECB 3.2.2.X
        case hdd
          when 0..2999 then
            return 0 #climate zone 4
          when 3000..3999 then
            return 1 #climate zone 5
          when 4000..4999 then
            return 2 #climate zone 6
          when 5000..5999 then
            return 3 #climate zone 7a
          when 6000..6999 then
            return 4 #climate zone 7b
          when 7000..1000000 then
            return 5 #climate zone 8
        end
      end

      #This model gets the climate zone name and returns the climate zone string.
      #@author phylroy.lopez@nrcan.gc.ca
      #@param hdd [Float]
      #@return [Fixnum] climate zone 4-8
      def self.get_climate_zone_name(hdd)
        case self.get_climate_zone_index(hdd)
          when 0 then
            return "4"
          when 1 then
            return "5" #climate zone 5
          when 2 then
            return "6" #climate zone 6
          when 3 then
            return "7a" #climate zone 7a
          when 4 then
            return "7b" #climate zone 7b
          when 5 then
            return "8" #climate zone 8
        end
      end


      #Set all external surface conductances to NECB values.
      #@author phylroy.lopez@nrcan.gc.ca
      #@param surface [String]
      #@param hdd [Float]
      #@param is_radiant [Boolian]
      #@param scaling_factor [Float]
      #@return [String] surface as RSI
      def self.set_necb_external_surface_conductance(surface, hdd, is_radiant = false, scaling_factor = 1.0)
        conductance_value = 0
        climate_zone_index = self.get_climate_zone_index(hdd)

        if surface.outsideBoundaryCondition.downcase == "outdoors"

          case surface.surfaceType.downcase
            when "wall"
              conductance_value = BTAP::Compliance::NECB2011::Data::Conductances::Wall[climate_zone_index] * scaling_factor
            when "floor"
              conductance_value = BTAP::Compliance::NECB2011::Data::Conductances::Floor[climate_zone_index] * scaling_factor
            when "roofceiling"
              conductance_value = BTAP::Compliance::NECB2011::Data::Conductances::Roof[climate_zone_index]
          end
          if (is_radiant)
            conductance_value = conductance_value * 0.80
          end
          return BTAP::Geometry::Surfaces::set_surfaces_construction_conductance([surface], conductance_value)
        end


        if surface.outsideBoundaryCondition.downcase.match(/ground/)
          case surface.surfaceType.downcase
            when "wall"
              conductance_value = BTAP::Compliance::NECB2011::Data::Conductances::GroundWall[BTAP::Compliance::NECB2011::get_climate_zone_index(@hdd)]
            when "floor"
              conductance_value = BTAP::Compliance::NECB2011::Data::Conductances::GroundFloor[BTAP::Compliance::NECB2011::get_climate_zone_index(@hdd)]
            when "roofceiling"
              conductance_value = BTAP::Compliance::NECB2011::Data::Conductances::GroundRoof[BTAP::Compliance::NECB2011::get_climate_zone_index(@hdd)]
          end
          if (is_radiant)
            conductance_value = conductance_value * 0.80
          end
          return BTAP::Geometry::Surfaces::set_surfaces_construction_conductance([surface], conductance_value)

        end
      end

      #Set all external subsurfaces (doors, windows, skylights) to NECB values.
      #@author phylroy.lopez@nrcan.gc.ca
      #@param subsurface [String]
      #@param hdd [Float]
      def self.set_necb_external_subsurface_conductance(subsurface, hdd)
        conductance_value = 0
        climate_zone_index = get_climate_zone_index(hdd)
        if subsurface.outsideBoundaryCondition.downcase.match("outdoors")
          case subsurface.subSurfaceType.downcase
            when /window/
              conductance_value = BTAP::Compliance::NECB2011::Data::Conductances::Window[climate_zone_index]
            when /door/
              conductance_value = BTAP::Compliance::NECB2011::Data::Conductance::Door[climate_zone_index]
          end
          subsurface.setRSI(1/conductance_value)
        end
      end







      def self.set_wildcard_schedules_to_dominant_building_schedule(model, runner = nil)

        new_sched_ruleset = OpenStudio::Model::DefaultScheduleSet.new(model) #initialize
        BTAP::runner_register("Info", "set_wildcard_schedules_to_dominant_building_schedule", runner)
        #Set wildcard schedules based on dominant schedule type in building.
        dominant_sched_type = BTAP::Compliance::NECB2011::determine_dominant_necb_schedule_type(model)
        #puts "dominant_sched_type = #{dominant_sched_type}"
        # find schedule set that corresponds to dominant schedule type
        model.getDefaultScheduleSets.sort.each do |sched_ruleset|
          # just check people schedule
          # TO DO: should make this smarter: check all schedules
          people_sched = sched_ruleset.numberofPeopleSchedule
          people_sched_name = people_sched.get.name.to_s unless people_sched.empty?

          search_string = "NECB-#{dominant_sched_type}"

          if people_sched.empty? == false
            if people_sched_name.include? search_string
              new_sched_ruleset = sched_ruleset
            end
          end
        end

        # replace the default schedule set for the space type with * to schedule ruleset with dominant schedule type

        model.getSpaces.sort.each do |space|
          #check to see if space space type has a "*" wildcard schedule.
          spacetype_name = space.spaceType.get.name.to_s unless space.spaceType.empty?
          if determine_necb_schedule_type(space).to_s == "*".to_s
            new_sched = (spacetype_name).to_s
            optional_spacetype = model.getSpaceTypeByName(new_sched)
            if optional_spacetype.empty?
              BTAP::runner_register("Error", "Cannot find NECB spacetype #{new_sched}", runner)
            else
              BTAP::runner_register("Info", "Setting wildcard spacetype #{spacetype_name} default schedule set to #{new_sched_ruleset.name}", runner)
              optional_spacetype.get.setDefaultScheduleSet(new_sched_ruleset) #this works!
            end
          end
        end # end of do |space|

        return true
      end

      def self.set_zones_thermostat_schedule_based_on_space_type_schedules(model, runner = nil)
        puts "in set_zones_thermostat_schedule_based_on_space_type_schedules"
        BTAP::runner_register("DEBUG", "Start-set_zones_thermostat_schedule_based_on_space_type_schedules", runner)
        model.getThermalZones.sort.each do |zone|
          BTAP::runner_register("DEBUG", "Zone = #{zone.name} Spaces =#{zone.spaces.size} ", runner)
          array = []

          zone.spaces.sort.each do |space|
            schedule_type = BTAP::Compliance::NECB2011::determine_necb_schedule_type(space).to_s
            BTAP::runner_register("DEBUG", "space name/type:#{space.name}/#{schedule_type}", runner)

            # if wildcard space type, need to get dominant schedule type
            if "*".to_s == schedule_type
              dominant_sched_type = BTAP::Compliance::NECB2011::determine_dominant_necb_schedule_type(model)
              schedule_type = dominant_sched_type
            end

            array << schedule_type
          end
          array.uniq!
          if array.size > 1
            BTAP::runner_register("Error", "#{zone.name} has spaces with different schedule types. Please ensure that all the spaces are of the same schedule type A to I.", runner)
            return false
          end


          htg_search_string = "NECB-#{array[0]}-Thermostat Setpoint-Heating"
          clg_search_string = "NECB-#{array[0]}-Thermostat Setpoint-Cooling"

          if model.getScheduleRulesetByName(htg_search_string).empty? == false
            htg_sched = model.getScheduleRulesetByName(htg_search_string).get
          else
            BTAP::runner_register("ERROR", "heating_thermostat_setpoint_schedule NECB-#{array[0]} does not exist", runner)
            return false
          end

          if model.getScheduleRulesetByName(clg_search_string).empty? == false
            clg_sched = model.getScheduleRulesetByName(clg_search_string).get
          else
            BTAP::runner_register("ERROR", "cooling_thermostat_setpoint_schedule NECB-#{array[0]} does not exist", runner)
            return false
          end

          name = "NECB-#{array[0]}-Thermostat Dual Setpoint Schedule"

          # If dual setpoint already exists, use that one, else create one       
          if model.getThermostatSetpointDualSetpointByName(name).empty? == false
            ds = model.getThermostatSetpointDualSetpointByName(name).get
          else
            ds = BTAP::Resources::Schedules::create_annual_thermostat_setpoint_dual_setpoint(model, name, htg_sched, clg_sched)
          end

          thermostatClone = ds.clone.to_ThermostatSetpointDualSetpoint.get
          zone.setThermostatSetpointDualSetpoint(thermostatClone)
          BTAP::runner_register("Info", "ThermalZone #{zone.name} set to DualSetpoint Schedule NECB-#{array[0]}", runner)

        end

        BTAP::runner_register("DEBUG", "END-set_zones_thermostat_schedule_based_on_space_type_schedules", runner)
        return true
      end



      # This method confirms if the type is the proper space type
      #@author phylroy.lopez@nrcan.gc.ca
      #@param type [String]
      #@return  [String] item
      def self.is_proper_spacetype(type)
        BTAP::Compliance::NECB2011::Data::SpaceTypeData.each do |item|
          if item[0] == type
            return item
          end
        end
        return false
      end


      #This model determines the dominant NECB schedule type
      #@param model [OpenStudio::model::Model] A model object
      #return s.each [String]
      def self.determine_dominant_necb_schedule_type(model)
        # lookup necb space type properties
        space_type_properties = model.find_objects(standards_data["space_types"], {"template" => 'NECB2011'})

        # Here is a hash to keep track of the m2 running total of spacetypes for each
        # sched type.
        s = Hash[
            "A", 0,
            "B", 0,
            "C", 0,
            "D", 0,
            "E", 0,
            "F", 0,
            "G", 0,
            "H", 0,
            "I", 0
        ]
        #iterate through spaces in building.
        wildcard_spaces = 0
        model.getSpaces.sort.each do |space|
          found_space_type = false
          #iterate through the NECB spacetype property table
          space_type_properties.each do |spacetype|
            unless space.spaceType.empty?
              if space.spaceType.get.standardsSpaceType.empty? || space.spaceType.get.standardsBuildingType.empty?
                OpenStudio::logFree(OpenStudio::Error, "openstudio.Standards.Model", "Space #{space.name} does not have a standardSpaceType defined")
                found_space_type = false
              elsif space.spaceType.get.standardsSpaceType.get == spacetype['space_type'] && space.spaceType.get.standardsBuildingType.get == spacetype['building_type']
                if "*" == spacetype['necb_schedule_type']
                  wildcard_spaces =+1
                else
                  s[spacetype['necb_schedule_type']] = s[spacetype['necb_schedule_type']] + space.floorArea() if "*" != spacetype['necb_schedule_type'] and "- undefined -" != spacetype['necb_schedule_type']
                end
                #puts "Found #{space.spaceType.get.name} schedule #{spacetype[2]} match with floor area of #{space.floorArea()}"
                found_space_type = true
              elsif "*" != spacetype['necb_schedule_type']
                #found wildcard..will not count to total. 
                found_space_type = true
              end
            end
          end
          raise ("Did not find #{space.spaceType.get.name} in NECB space types.") if found_space_type == false
        end
        #finds max value and returns NECB schedule letter.
        raise("Only wildcard spaces in model. You need to define the actual spaces. ") if wildcard_spaces == model.getSpaces.size
        dominant_schedule = s.each {|k, v| return k.to_s if v == s.values.max}
        return dominant_schedule
      end

      #This method determines the spacetype schedule type. This will re
      #@author phylroy.lopez@nrcan.gc.ca
      #@param space [String]
      #@return [String]:["A","B","C","D","E","F","G","H","I"] spacetype
      def self.determine_necb_schedule_type(space)
        raise ("Undefined spacetype for space #{space.get.name}) if space.spaceType.empty?") if space.spaceType.empty?
        raise ("Undefined standardsSpaceType or StandardsBuildingType for space #{space.spaceType.get.name}) if space.spaceType.empty?") if space.spaceType.get.standardsSpaceType.empty? | space.spaceType.get.standardsBuildingType.empty?
        space_type_properties = space.model.find_object(standards_data["space_types"], {"template" => 'NECB2011', "space_type" => space.spaceType.get.standardsSpaceType.get, "building_type" => space.spaceType.get.standardsBuildingType.get})
        return space_type_properties['necb_schedule_type'].strip
      end


      def self.necb_spacetype_system_selection(model, heatingDesignLoad = nil, coolingDesignLoad = nil)
        spacezoning_data = Struct.new(
            :space, # the space object
            :space_name, # the space name
            :building_type_name, # space type name
            :space_type_name, # space type name
            :necb_hvac_system_selection_type, #
            :system_number, # the necb system type
            :number_of_stories, #number of stories
            :horizontal_placement, # the horizontal placement (norht, south, east, west, core)
            :vertical_placment, # the vertical placement ( ground, top, both, middle )
            :people_obj, # Spacetype people object
            :heating_capacity,
            :cooling_capacity,
            :is_dwelling_unit, #Checks if it is a dwelling unit.
            :is_wildcard)


        #Array to store schedule objects
        schedule_type_array = []


        #find the number of stories in the model this include multipliers.
        number_of_stories = 	model.getBuilding.standardsNumberOfAboveGroundStories()
        if number_of_stories.empty?
          raise ("Number of above ground stories not present in geometry model. Please ensure this is defined in your Building Object")
        else
          number_of_stories = number_of_stories.get
        end

        #set up system array containers. These will contain the spaces associated with the system types.
        space_zoning_data_array = []

        #First pass of spaces to collect information into the space_zoning_data_array .
        model.getSpaces.sort.each do |space|


          #this will get the spacetype system index 8.4.4.8A  from the SpaceTypeData and BuildingTypeData in  (1-12)
          space_system_index = nil
          if space.spaceType.empty?
            space_system_index = nil
          else
            #gets row information from standards spreadsheet.
            space_type_property = space.model.find_object(standards_data["space_types"], {"template" => 'NECB2011', "space_type" => space.spaceType.get.standardsSpaceType.get, "building_type" => space.spaceType.get.standardsBuildingType.get})
            raise("could not find necb system selection type for space: #{space.name} and spacetype #{space.spaceType.get.standardsSpaceType.get}") if space_type_property.nil?
            #stores the Building or SpaceType System type name.
            necb_hvac_system_selection_type = space_type_property['necb_hvac_system_selection_type']
          end


          #Get the heating and cooling load for the space. Only Zones with a defined thermostat will have a load.
          #Make sure we don't have sideeffects by changing the argument variables.
          cooling_load = coolingDesignLoad
          heating_load = heatingDesignLoad
          if space.spaceType.get.standardsSpaceType.get == "- undefined -"
            cooling_load = 0.0
            heating_load = 0.0
          else
            cooling_load = space.thermalZone.get.coolingDesignLoad.get * space.floorArea * space.multiplier / 1000.0 if cooling_load.nil?
            heating_load = space.thermalZone.get.heatingDesignLoad.get * space.floorArea * space.multiplier / 1000.0 if heating_load.nil?
          end

          #identify space-system_index and assign the right NECB system type 1-7.
          system = nil
          is_dwelling_unit = false
          case necb_hvac_system_selection_type
            when nil
              raise ("#{space.name} does not have an NECB system association. Please define a NECB HVAC System Selection Type in the google docs standards database.")
            when 0, "- undefined -"
              #These are spaces are undefined...so they are unconditioned and have no loads other than infiltration and no systems
              system = 0
            when "Assembly Area" #Assembly Area.
              if number_of_stories <= 4
                system = 3
              else
                system = 6
              end

            when "Automotive Area"
              system = 4

            when "Data Processing Area"
              if coolingDesignLoad > 20 #KW...need a sizing run.
                system = 2
              else
                system = 1
              end

            when "General Area" #[3,6]
              if number_of_stories <= 2
                system = 3
              else
                system = 6
              end

            when "Historical Collections Area" #[2],
              system = 2

            when "Hospital Area" #[3],
              system = 3

            when "Indoor Arena" #,[7],
              system = 7

            when "Industrial Area" #  [3] this need some thought.
              system = 3

            when "Residential/Accomodation Area" #,[1], this needs some thought.
              system = 1
              is_dwelling_unit = true

            when "Sleeping Area" #[3],
              system = 3
              is_dwelling_unit = true

            when "Supermarket/Food Services Area" #[3,4],
              system = 3

            when "Supermarket/Food Services Area - vented"
              system = 4

            when "Warehouse Area"
              system = 4

            when "Warehouse Area - refrigerated"
              system = 5
            when "Wildcard"
              system = nil
              is_wildcard = true
            else
              raise ("NECB HVAC System Selection Type #{necb_hvac_system_selection_type} not valid")
          end
          #get placement on floor, core or perimeter and if a top, bottom, middle or single story.
          horizontal_placement, vertical_placement = BTAP::Geometry::Spaces::get_space_placement(space)
          #dump all info into an array for debugging and iteration.
          unless space.spaceType.empty?
            space_type_name = space.spaceType.get.standardsSpaceType.get
            building_type_name = space.spaceType.get.standardsBuildingType.get
            space_zoning_data_array << spacezoning_data.new(space,
                                                            space.name.get,
                                                            building_type_name,
                                                            space_type_name,
                                                            necb_hvac_system_selection_type,
                                                            system,
                                                            number_of_stories,
                                                            horizontal_placement,
                                                            vertical_placement,
                                                            space.spaceType.get.people,
                                                            heating_load,
                                                            cooling_load,
                                                            is_dwelling_unit,
                                                            is_wildcard
            )
            schedule_type_array << BTAP::Compliance::NECB2011::determine_necb_schedule_type(space).to_s
          end
        end


        return schedule_type_array.uniq!, space_zoning_data_array
      end


      # This method will take a model that uses NECB2011 spacetypes , and..
      # 1. Create a building story schema.
      # 2. Remove all existing Thermal Zone defintions.
      # 3. Create new thermal zones based on the following definitions.
      # Rule1 all zones must contain only the same schedule / occupancy schedule.
      # Rule2 zones must cater to similar solar gains (N,E,S,W)
      # Rule3 zones must not pass from floor to floor. They must be contained to a single floor or level.
      # Rule4 Wildcard spaces will be associated with the nearest zone of similar schedule type in which is shared most of it's internal surface with.
      # Rule5 For NECB zones must contain spaces of similar system type only.
      # Rule6 Residential / dwelling units must not share systems with other space types.
      # @author phylroy.lopez@nrcan.gc.ca
      # @param model [OpenStudio::model::Model] A model object
      # @return [String] system_zone_array
      def self.necb_autozone_and_autosystem(
          model = nil,
              runner = nil,
              use_ideal_air_loads = false,
              boiler_fueltype = "NaturalGas",
              mau_type = true,
              mau_heating_coil_type = "Hot Water",
              baseboard_type = "Hot Water",
              chiller_type = "Scroll",
              mua_cooling_type = "DX",
              heating_coil_types_sys3 = "Gas",
              heating_coil_types_sys4 = "Gas",
              heating_coil_types_sys6 = "Hot Water",
              fan_type = "AF_or_BI_rdg_fancurve",
              swh_fueltype = "NaturalGas",
              building_type = nil)

        #Create a data struct for the space to system to placement information.


        #system assignment.
        unless ["NaturalGas", "Electricity", "PropaneGas", "FuelOil#1", "FuelOil#2", "Coal", "Diesel", "Gasoline", "OtherFuel1"].include?(boiler_fueltype)
          BTAP::runner_register("ERROR", "boiler_fueltype = #{boiler_fueltype}", runner)
          return
        end

        unless [true, false].include?(mau_type)
          BTAP::runner_register("ERROR", "mau_type = #{mau_type}", runner)
          return
        end

        unless ["Hot Water", "Electric"].include?(mau_heating_coil_type)
          BTAP::runner_register("ERROR", "mau_heating_coil_type = #{mau_heating_coil_type}", runner)
          return false
        end

        unless ["Hot Water", "Electric"].include?(baseboard_type)
          BTAP::runner_register("ERROR", "baseboard_type = #{baseboard_type}", runner)
          return false
        end


        unless ["Scroll", "Centrifugal", "Rotary Screw", "Reciprocating"].include?(chiller_type)
          BTAP::runner_register("ERROR", "chiller_type = #{chiller_type}", runner)
          return false
        end
        unless ["DX", "Hydronic"].include?(mua_cooling_type)
          BTAP::runner_register("ERROR", "mua_cooling_type = #{mua_cooling_type}", runner)
          return false
        end

        unless ["Electric", "Gas", "DX"].include?(heating_coil_types_sys3)
          BTAP::runner_register("ERROR", "heating_coil_types_sys3 = #{heating_coil_types_sys3}", runner)
          return false
        end

        unless ["Electric", "Gas", "DX"].include?(heating_coil_types_sys4)
          BTAP::runner_register("ERROR", "heating_coil_types_sys4 = #{heating_coil_types_sys4}", runner)
          return false
        end

        unless ["Hot Water", "Electric"].include?(heating_coil_types_sys6)
          BTAP::runner_register("ERROR", "heating_coil_types_sys6 = #{heating_coil_types_sys6}", runner)
          return false
        end

        unless ["AF_or_BI_rdg_fancurve", "AF_or_BI_inletvanes", "fc_inletvanes", "var_speed_drive"].include?(fan_type)
          BTAP::runner_register("ERROR", "fan_type = #{fan_type}", runner)
          return false
        end
        # REPEATED CODE!!
        unless ["Electric", "Hot Water"].include?(heating_coil_types_sys6)
          BTAP::runner_register("ERROR", "heating_coil_types_sys6 = #{heating_coil_types_sys6}", runner)
          return false
        end
        # REPEATED CODE!!
        unless ["Electric", "Gas"].include?(heating_coil_types_sys4)
          BTAP::runner_register("ERROR", "heating_coil_types_sys4 = #{heating_coil_types_sys4}", runner)
          return false
        end

        # Ensure that floors have been assigned by user.
        raise("No building stories have been defined.. User must define building stories and spaces in model.") unless model.getBuildingStorys.size > 0
        #BTAP::Geometry::BuildingStoreys::auto_assign_stories(model)

        #this method will determine the spaces that should be set to each system
        schedule_type_array, space_zoning_data_array = self.necb_spacetype_system_selection(model, nil, nil)

        #Deal with Wildcard spaces. Might wish to have logic to do coridors first.
        space_zoning_data_array.sort{|obj1, obj2| obj1.space_name <=> obj2.space_name}.each do |space_zone_data|
          #If it is a wildcard space.
          if space_zone_data.system_number.nil?
            #iterate through all adjacent spaces from largest shared wall area to smallest.
            # Set system type to match first space system that is not nil.
            adj_spaces = space_zone_data.space.get_adjacent_spaces_with_shared_wall_areas(true)
            if adj_spaces.nil?
              puts ("Warning: No adjacent spaces for #{space_zone_data.space.name} on same floor, looking for others above and below to set system")
              adj_spaces = space_zone_data.space.get_adjacent_spaces_with_shared_wall_areas(false)
            end
            adj_spaces.sort.each do |adj_space|
              #if there are no adjacent spaces. Raise an error.
              raise ("Could not determine adj space to space #{space_zone_data.space.name.get}") if adj_space.nil?
              adj_space_data = space_zoning_data_array.find {|data| data.space == adj_space[0]}
              if adj_space_data.system_number.nil?
                next
              else
                space_zone_data.system_number = adj_space_data.system_number
                puts "#{space_zone_data.space.name.get}"
                break
              end
            end
            raise ("Could not determine adj space system to space #{space_zone_data.space.name.get}") if space_zone_data.system_number.nil?
          end
        end


        #remove any thermal zones used for sizing to start fresh. Should only do this after the above system selection method.
        model.getThermalZones.sort.each {|zone| zone.remove}


        #now lets apply the rules.
        # Rule1 all zones must contain only the same schedule / occupancy schedule.
        # Rule2 zones must cater to similar solar gains (N,E,S,W)
        # Rule3 zones must not pass from floor to floor. They must be contained to a single floor or level.
        # Rule4 Wildcard spaces will be associated with the nearest zone of similar schedule type in which is shared most of it's internal surface with.
        # Rule5 NECB zones must contain spaces of similar system type only.
        # Rule6 Multiplier zone will be part of the floor and orientation of the base space.
        # Rule7 Residential / dwelling units must not share systems with other space types.
        #Array of system types of Array of Spaces
        system_zone_array = []
        #Lets iterate by system
        (0..7).each do |system_number|
          system_zone_array[system_number] = []
          #iterate by story
          story_counter = 0
          model.getBuildingStorys.sort.each do |story|
            #puts "Story:#{story}"
            story_counter = story_counter + 1
            #iterate by operation schedule type.
            schedule_type_array.each do |schedule_type|
              #iterate by horizontal location
              ["north", "east", "west", "south", "core"].each do |horizontal_placement|
                #puts "horizontal_placement:#{horizontal_placement}"
                [true, false].each do |is_dwelling_unit|
                  space_array = Array.new
                  space_zoning_data_array.each do |space_info|
                    #puts "Spacename: #{space_info.space.name}:#{space_info.space.spaceType.get.name}"
                    if space_info.system_number == system_number and
                        space_info.space.buildingStory.get == story and
                        BTAP::Compliance::NECB2011::determine_necb_schedule_type(space_info.space).to_s == schedule_type and
                        space_info.horizontal_placement == horizontal_placement and
                        space_info.is_dwelling_unit == is_dwelling_unit
                      space_array << space_info.space
                    end
                  end

                  #create Thermal Zone if space_array is not empty.
                  if space_array.size > 0
                    # Process spaces that have multipliers associated with them first.
                    # This map define the multipliers for spaces with multipliers not equals to 1
                    space_multiplier_map = {}
                    # 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 new zone and add the spaces to it.
                    space_array.each do |space|
                      # Create thermalzone for each space.
                      thermal_zone = OpenStudio::Model::ThermalZone.new(model)
                      # Create a more informative space name.
                      thermal_zone.setName("Sp-#{space.name} Sys-#{system_number.to_s} Flr-#{story_counter.to_s} Sch-#{schedule_type.to_s} HPlcmt-#{horizontal_placement} ZN")
                      # Add zone mulitplier if required.
                      thermal_zone.setMultiplier(space_multiplier_map[space.name.to_s]) unless space_multiplier_map[space.name.to_s].nil?
                      # Space to thermal zone. (for archetype work it is one to one)
                      space.setThermalZone(thermal_zone)
                      # Get thermostat for space type if it already exists.
                      space_type_name = space.spaceType.get.name.get
                      thermostat_name = space_type_name + ' Thermostat'
                      thermostat = model.getThermostatSetpointDualSetpointByName(thermostat_name)
                      if thermostat.empty?
                        OpenStudio::logFree(OpenStudio::Error, 'openstudio.model.Model', "Thermostat #{thermostat_name} not found for space name: #{space.name} ZN")
                        raise (" Thermostat #{thermostat_name} not found for space name: #{space.name}")
                      else
                        thermostatClone = thermostat.get.clone(model).to_ThermostatSetpointDualSetpoint.get
                        thermal_zone.setThermostatSetpointDualSetpoint(thermostatClone)
                      end
                      # Add thermal to zone system number.
                      system_zone_array[system_number] << thermal_zone
                    end
                  end
                end
              end
            end
          end
        end #system iteration

        #Create and assign the zones to the systems.
        unless use_ideal_air_loads == true
          hw_loop_needed = false
          system_zone_array.each_with_index do |zones, system_index|
            next if zones.size == 0
            if (system_index == 1 && (mau_heating_coil_type == 'Hot Water' || baseboard_type == 'Hot Water'))
              hw_loop_needed = true
            elsif (system_index == 2 || system_index == 5 || system_index == 7)
              hw_loop_needed = true
            elsif ((system_index == 3 || system_index == 4) && baseboard_type == 'Hot Water')
              hw_loop_needed = true
            elsif (system_index == 6 && (mau_heating_coil_type == 'Hot Water' || baseboard_type == 'Hot Water'))
              hw_loop_needed = true
            end
            if (hw_loop_needed) then
              break
            end
          end
          if (hw_loop_needed)
            hw_loop = OpenStudio::Model::PlantLoop.new(model)
            always_on = model.alwaysOnDiscreteSchedule
            BTAP::Resources::HVAC::HVACTemplates::NECB2011::setup_hw_loop_with_components(model, hw_loop, boiler_fueltype, always_on)
          end
          system_zone_array.each_with_index do |zones, system_index|
            #skip if no thermal zones for this system.
            next if zones.size == 0
            #            puts "Zone Names for System #{system_index}"
            #            puts "system_index = #{system_index}"
            case system_index
              when 0, nil
                #Do nothing no system assigned to zone. Used for Unconditioned spaces
              when 1
                BTAP::Resources::HVAC::HVACTemplates::NECB2011::assign_zones_sys1(model, zones, boiler_fueltype, mau_type, mau_heating_coil_type, baseboard_type, hw_loop)
              when 2
                BTAP::Resources::HVAC::HVACTemplates::NECB2011::assign_zones_sys2(model, zones, boiler_fueltype, chiller_type, mua_cooling_type)
              when 3
                BTAP::Resources::HVAC::HVACTemplates::NECB2011::assign_zones_sys3(model, zones, boiler_fueltype, heating_coil_types_sys3, baseboard_type, hw_loop)
              when 4
                BTAP::Resources::HVAC::HVACTemplates::NECB2011::assign_zones_sys4(model, zones, boiler_fueltype, heating_coil_types_sys4, baseboard_type, hw_loop)
              when 5
                BTAP::Resources::HVAC::HVACTemplates::NECB2011::assign_zones_sys5(model, zones, boiler_fueltype, chiller_type, mua_cooling_type)
              when 6
                BTAP::Resources::HVAC::HVACTemplates::NECB2011::assign_zones_sys6(model, zones, boiler_fueltype, heating_coil_types_sys6, baseboard_type, chiller_type, fan_type, hw_loop)
              when 7
                BTAP::Resources::HVAC::HVACTemplates::NECB2011::assign_zones_sys7(model, zones, boiler_fueltype, chiller_type, mua_cooling_type)
            end
          end
        else
          #otherwise use ideal loads.
          model.getThermalZones.sort.each do |thermal_zone|
            thermal_zone_ideal_loads = OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(model)
            thermal_zone_ideal_loads.addToThermalZone(thermal_zone)
          end
        end
        #Check to ensure that all spaces are assigned to zones except undefined ones.
        errors = []
        model.getSpaces.sort.each do |space|
          if space.thermalZone.empty? and space.spaceType.get.name.get != 'Space Function - undefined -'
            errors << "space #{space.name} with spacetype #{space.spaceType.get.name.get} was not assigned a thermalzone."
          end
        end
        if errors.size > 0
          raise(" #{errors}")
        end
      end #
    end
  end #Compliance
end