# *******************************************************************************
# Honeybee OpenStudio Gem, Copyright (c) 2020, Alliance for Sustainable
# Energy, LLC, Ladybug Tools LLC and other contributors. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# (1) Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# (2) Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# (3) Neither the name of the copyright holder nor the names of any contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission from the respective party.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
# UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
# THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# *******************************************************************************

require 'honeybee/load/service_hot_water'

require 'to_openstudio/model_object'

module Honeybee
  class ServiceHotWaterAbridged
    @@max_target_temp = 60
    @@max_temp_schedule = nil
    @@shw_connections = {}
    @@shw_rates = {}
    @@hp_deadband = 4
    @@sys_count = 1

    def shw_connections
      @@shw_connections
    end

    def find_existing_openstudio_object(openstudio_model)
      model_shw = openstudio_model.getWaterUseEquipmentByName(@hash[:identifier])
      return model_shw.get unless model_shw.empty?
      nil
    end

    def add_hot_water_plants(openstudio_model, shw_hashes)
      # add district hot water loops to supply all of the shw_connections
      shw_hashes.each do |shw_hash|
        # create the plant loop
        hot_water_plant = OpenStudio::Model::PlantLoop.new(openstudio_model)
        hot_water_plant.setName('SHW Loop ' + shw_hash[:identifier])
        hot_water_plant.setMaximumLoopTemperature(@@max_target_temp)
        hot_water_plant.setMinimumLoopTemperature(10)  # default value in C from OpenStudio Application

        # edit the sizing information to be for a hot water loop
        loop_sizing = hot_water_plant.sizingPlant()
        loop_sizing.setLoopType('Heating')
        loop_sizing.setDesignLoopExitTemperature(@@max_target_temp)  
        loop_sizing.setLoopDesignTemperatureDifference(5)  # default value in C from OpenStudio Application

        # add a setpoint manager for the loop
        hot_sch = @@max_temp_schedule
        if @@max_temp_schedule.nil?
          hot_sch_name = @@max_target_temp.to_s + 'C Hot Water'
          hot_sch = create_constant_schedule(openstudio_model, hot_sch_name, @@max_target_temp)
        end
        sp_manager = OpenStudio::Model::SetpointManagerScheduled.new(openstudio_model, hot_sch)
        sp_manager.addToNode(hot_water_plant.supplyOutletNode())

        # add a constant speed pump for the loop
        hot_water_pump = OpenStudio::Model::PumpConstantSpeed.new(openstudio_model)
        hot_water_pump.setName('SHW Pump' + @@sys_count.to_s)
        hot_water_pump.setRatedPumpHead(29891)  # default value in Pa from OpenStudio Application
        hot_water_pump.setMotorEfficiency(0.9)  # default value from OpenStudio Application
        hot_water_pump.addToNode(hot_water_plant.supplyInletNode())

        eq_type = shw_hash[:equipment_type]
        if eq_type == 'Default_District_SHW'
          # add a district heating system to supply the heat for the loop
          district_hw = OpenStudio::Model::DistrictHeating.new(openstudio_model)
          district_hw.setName('Service Hot Water District Heat')
          district_hw.setNominalCapacity(1000000)
          hot_water_plant.addSupplyBranchForComponent(district_hw)
        elsif eq_type == 'Gas_WaterHeater' || eq_type == 'Electric_WaterHeater' || eq_type == 'HeatPump_WaterHeater'
          # add a water heater to supply the heat for the loop
          heater = OpenStudio::Model::WaterHeaterMixed.new(openstudio_model)
          if eq_type == 'Electric_WaterHeater' || eq_type == 'HeatPump_WaterHeater'
            heater.setHeaterFuelType('Electricity')
            heater.setOffCycleParasiticFuelType('Electricity')
            heater.setOnCycleParasiticFuelType('Electricity')
          end
          heater.setName('SHW WaterHeater' + @@sys_count.to_s)

          # set the water heater efficiency
          if eq_type == 'HeatPump_WaterHeater'
            heater.setHeaterThermalEfficiency(1.0)
          elsif shw_hash[:heater_efficiency].nil?
            if eq_type == 'Electric_WaterHeater'
              heater.setHeaterThermalEfficiency(1.0)
            else
              heater.setHeaterThermalEfficiency(0.8)
            end
          else
            heater.setHeaterThermalEfficiency(shw_hash[:heater_efficiency])
          end

          # set the ambient condition of the water tank
          to_thermal_zone = false
          unless shw_hash[:ambient_condition].nil?
            if shw_hash[:ambient_condition].is_a? Numeric
              target_sch_name = shw_hash[:ambient_condition].to_s + 'C Ambient Condition'
              target_sch = create_constant_schedule(
                openstudio_model, target_sch_name, shw_hash[:ambient_condition])
              heater.setAmbientTemperatureSchedule(target_sch)
            else
              source_zone_ref = openstudio_model.getThermalZoneByName(shw_hash[:ambient_condition])
              unless source_zone_ref.empty?
                source_zone = source_zone_ref.get
                heater.setAmbientTemperatureThermalZone(source_zone)
              end
              heater.setAmbientTemperatureIndicator('ThermalZone')
              to_thermal_zone = true
            end
          else
            target_sch_name = '22C Ambient Condition'
            target_sch = create_constant_schedule(openstudio_model, target_sch_name, 22)
            heater.setAmbientTemperatureSchedule(target_sch)
          end

          # set the ambient loss coefficient
          if to_thermal_zone
            unless shw_hash[:ambient_loss_coefficient].nil?
              heater.setOffCycleLossFractiontoThermalZone(
                shw_hash[:ambient_loss_coefficient])
              heater.setOnCycleLossFractiontoThermalZone(
                shw_hash[:ambient_loss_coefficient])
            else
              heater.setOffCycleLossFractiontoThermalZone(6)
              heater.setOnCycleLossFractiontoThermalZone(6)
            end
          else
            unless shw_hash[:ambient_loss_coefficient].nil?
              heater.setOffCycleLossCoefficienttoAmbientTemperature(
                shw_hash[:ambient_loss_coefficient])
              heater.setOnCycleLossCoefficienttoAmbientTemperature(
                shw_hash[:ambient_loss_coefficient])
            else
              heater.setOffCycleLossCoefficienttoAmbientTemperature(6)
              heater.setOnCycleLossCoefficienttoAmbientTemperature(6)
            end
          end

          # ensure that the tank is sized appropriately and add it to the loop
          heater.setHeaterMaximumCapacity(1000000)
          heater.setTankVolume(@@shw_rates[shw_hash[:identifier]])
          hot_water_plant.addSupplyBranchForComponent(heater)
          
          # if it's a heat pump system, then add the pump
          if eq_type == 'HeatPump_WaterHeater'
            # create a coil for the heat pump
            heat_pump = OpenStudio::Model::CoilWaterHeatingAirToWaterHeatPump.new(openstudio_model)
            heat_pump.setName('HPWH DX Coil' + @@sys_count.to_s)
            if shw_hash[:heater_efficiency].nil?
              heat_pump.setRatedCOP(3.5)
            else
              heat_pump.setRatedCOP(shw_hash[:heater_efficiency])
            end

            # add a fan for the heat pump system
            fan = OpenStudio::Model::FanOnOff.new(openstudio_model)
            fan.setName('HPWH Fan' + @@sys_count.to_s)
            fan.setEndUseSubcategory('Water Systems')
            setpt_sch = create_constant_schedule(
              openstudio_model, 'HPWH Setpoint' + @@sys_count.to_s, @@max_target_temp + (@@hp_deadband * 2))
            inlet_sch = create_constant_schedule(
              openstudio_model, 'Inlet Air Mixer Fraction' + @@sys_count.to_s, 0.2)

            # add a water heater to supply the heat for the loop
            heat_sys = OpenStudio::Model::WaterHeaterHeatPump.new(
              openstudio_model, heat_pump, heater, fan, setpt_sch, inlet_sch)
            heat_sys.setDeadBandTemperatureDifference(@@hp_deadband)
            
            source_zone_ref = openstudio_model.getThermalZoneByName(shw_hash[:ambient_condition])
            unless source_zone_ref.empty?
              source_zone = source_zone_ref.get
              heat_sys.addToThermalZone(source_zone)
            end
            heat_sys.setName('SHW WaterHeater HeatPump' + @@sys_count.to_s)
          end

        elsif eq_type == 'Gas_TanklessHeater' || eq_type == 'Electric_TanklessHeater'
          # add a boiler to supply the heat for the loop
          heater = OpenStudio::Model::BoilerHotWater.new(openstudio_model)
          if eq_type == 'Electric_TanklessHeater'
            heater.setFuelType('Electricity')
          end
          heater.setName('SHW Tankless WaterHeater' + @@sys_count.to_s)
          heater.setEndUseSubcategory('Water Systems')

          # set the water heater efficiency
          unless shw_hash[:heater_efficiency].nil?
            heater.setNominalThermalEfficiency(shw_hash[:heater_efficiency])
          else
            if eq_type == 'Electric_TanklessHeater'
              heater.setNominalThermalEfficiency(1.0)
            else
              heater.setNominalThermalEfficiency(0.8)
            end
          end

          # ensure that the boiler is sized appropriately and add it to the loop
          heater.setNominalCapacity(1000000)
          hot_water_plant.addSupplyBranchForComponent(heater)
        end

        # add all of the water use connections to the loop and total the capacity
        @@shw_connections[shw_hash[:identifier]].each do |shw_conn|
          hot_water_plant.addDemandBranchForComponent(shw_conn)
        end
        @@sys_count = @@sys_count + 1
      end
    end

    def to_openstudio(openstudio_model, os_space, shw_name)
      # create water use equipment + connection and set identifier
      os_shw_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(openstudio_model)
      os_shw = OpenStudio::Model::WaterUseEquipment.new(os_shw_def)
      unique_id = @hash[:identifier] + '..' + os_space.nameString
      os_shw_def.setName(unique_id)
      os_shw.setName(unique_id)

      # assign the flow of water
      total_flow = (@hash[:flow_per_area].to_f * os_space.floorArea) / 3600000
      os_shw_def.setPeakFlowRate(total_flow)
      os_shw_def.setEndUseSubcategory('General')
      if @@shw_rates[shw_name].nil?
        @@shw_rates[shw_name] = 0
      end
      @@shw_rates[shw_name] = @@shw_rates[shw_name] + (total_flow * 3600)

      # assign schedule
      shw_schedule = openstudio_model.getScheduleByName(@hash[:schedule])
      unless shw_schedule.empty?
        shw_schedule_object = shw_schedule.get
        os_shw.setFlowRateFractionSchedule(shw_schedule_object)
      end

      # assign the hot water temperature
      target_temp = defaults[:target_temperature][:default]
      if @hash[:target_temperature]
        target_temp = @hash[:target_temperature]
      end
      target_sch_name = target_temp.to_s + 'C Hot Water'
      target_water_sch = create_constant_schedule(openstudio_model, target_sch_name, target_temp)
      os_shw_def.setTargetTemperatureSchedule(target_water_sch)

      # create the hot water connection with same temperature as target temperature
      os_shw_conn = OpenStudio::Model::WaterUseConnections.new(openstudio_model)
      os_shw_conn.addWaterUseEquipment(os_shw)
      os_shw_conn.setHotWaterSupplyTemperatureSchedule(target_water_sch)
      if target_temp > @@max_target_temp
        @@max_target_temp = target_temp
        @@max_temp_schedule = target_water_sch
      end
      if shw_name.nil?
        shw_name = 'default_district_shw'
      end
      if @@shw_connections[shw_name].nil?
        @@shw_connections[shw_name] = []
      end
      @@shw_connections[shw_name] << os_shw_conn

      # assign sensible fraction if it exists
      sens_fract = defaults[:sensible_fraction][:default]
      if @hash[:sensible_fraction]
        sens_fract = @hash[:sensible_fraction]
      end
      sens_sch_name = sens_fract.to_s + ' Hot Water Sensible Fraction'
      sens_fract_sch = create_constant_schedule(openstudio_model, sens_sch_name, sens_fract)
      os_shw_def.setSensibleFractionSchedule(sens_fract_sch)

      # assign latent fraction if it exists
      lat_fract = defaults[:latent_fraction][:default]
      if @hash[:latent_fraction]
        lat_fract = @hash[:latent_fraction]
      end
      lat_sch_name = lat_fract.to_s + ' Hot Water Latent Fraction'
      lat_fract_sch = create_constant_schedule(openstudio_model, lat_sch_name, lat_fract)
      os_shw_def.setLatentFractionSchedule(lat_fract_sch)

      # assign the service hot water to the space
      os_shw.setSpace(os_space)

      os_shw
    end

    private

    def create_constant_schedule(openstudio_model, schedule_name, value)
      # check if a constant schedule already exists and, if not, create it
      exist_schedule = openstudio_model.getScheduleByName(schedule_name)
      if exist_schedule.empty?  # create the schedule
        os_sch_ruleset = OpenStudio::Model::ScheduleRuleset.new(openstudio_model, value)
        os_sch_ruleset.setName(schedule_name)
      else
        os_sch_ruleset = exist_schedule.get
      end
      os_sch_ruleset
    end

  end #ServiceHotWaterAbridged
end #Honeybee