# *******************************************************************************
# OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
# See also https://openstudio.net/license
# *******************************************************************************

module OsLib_QAQC
  # include any general notes about QAQC method here

  # checks the number of unmet hours in the model
  def check_mech_sys_capacity(category, options, target_standard, name_only = false)
    # summary of the check
    check_elems = OpenStudio::AttributeVector.new
    check_elems << OpenStudio::Attribute.new('name', 'Mechanical System Capacity')
    check_elems << OpenStudio::Attribute.new('category', category)
    check_elems << OpenStudio::Attribute.new('description', 'Check HVAC capacity against ASHRAE rules of thumb for chiller max flow rate, air loop max flow rate, air loop cooling capciaty, and zone heating capcaity. Zone heating check will skip thermal zones without any exterior exposure, and thermal zones that are not conditioned.')
    check_elems << OpenStudio::Attribute.new('min_pass', "Var")
    check_elems << OpenStudio::Attribute.new('max_pass', "Var")
    
    # stop here if only name is requested this is used to populate display name for arguments
    if name_only == true
      results = []
      check_elems.each do |elem|
        next if ['Double','Integer'].include? (elem.valueType.valueDescription)
        results << elem.valueAsString
      end
      return results
    end

    # Versions of OpenStudio greater than 2.4.0 use a modified version of
    # openstudio-standards with different method calls.  These methods
    # require a "Standard" object instead of the standard being passed into method calls.
    # This Standard object is used throughout the QAQC check.
    if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.3')
      use_old_gem_code = true
    else
      use_old_gem_code = false
      std = Standard.build(target_standard)
    end

    begin
      # check max flow rate of chillers in model
      @model.getPlantLoops.sort.each do |plant_loop|
        # next if no chiller on plant loop
        chillers = []
        plant_loop.supplyComponents.each do |sc|
          if sc.to_ChillerElectricEIR.is_initialized
            chillers << sc.to_ChillerElectricEIR.get
          end
        end
        next if chillers.empty?

        # gather targets for chiller capacity
        chiller_max_flow_rate_target = options['chiller_max_flow_rate']['target']
        chiller_max_flow_rate_fraction_min = options['chiller_max_flow_rate']['min']
        chiller_max_flow_rate_fraction_max = options['chiller_max_flow_rate']['max']
        chiller_max_flow_rate_units_ip = options['chiller_max_flow_rate']['units'] # gal/ton*min
        # string above or display only, for converstion 12000 Btu/h per ton

        # get capacity of loop (not individual chiller but entire loop)
        if use_old_gem_code
          total_cooling_capacity_w = plant_loop.total_cooling_capacity
        else
          total_cooling_capacity_w = std.plant_loop_total_cooling_capacity(plant_loop)
        end
        total_cooling_capacity_ton = OpenStudio.convert(total_cooling_capacity_w, 'W', 'Btu/h').get / 12000.0

        # get the max flow rate (through plant, not specific chiller)
        if use_old_gem_code
          maximum_loop_flow_rate = plant_loop.find_maximum_loop_flow_rate
        else
          maximum_loop_flow_rate = std.plant_loop_find_maximum_loop_flow_rate(plant_loop)
        end
        maximum_loop_flow_rate_ip = OpenStudio.convert(maximum_loop_flow_rate, 'm^3/s', 'gal/min').get

        # calculate the flow per tons of cooling
        model_flow_rate_per_ton_cooling_ip = maximum_loop_flow_rate_ip / total_cooling_capacity_ton

        # check flow rate per capacity
        if model_flow_rate_per_ton_cooling_ip < chiller_max_flow_rate_target * (1.0 - chiller_max_flow_rate_fraction_min)
          check_elems <<  OpenStudio::Attribute.new('flag', "Flow Rate of #{model_flow_rate_per_ton_cooling_ip.round(2)} #{chiller_max_flow_rate_units_ip} for #{plant_loop.name.get} is more than #{chiller_max_flow_rate_fraction_min * 100} % below the typical value of #{chiller_max_flow_rate_target.round(2)} #{chiller_max_flow_rate_units_ip}.")
        elsif model_flow_rate_per_ton_cooling_ip > chiller_max_flow_rate_target * (1.0 + chiller_max_flow_rate_fraction_max)
          check_elems <<  OpenStudio::Attribute.new('flag', "Flow Rate of #{model_flow_rate_per_ton_cooling_ip.round(2)} #{chiller_max_flow_rate_units_ip} for #{plant_loop.name.get} is more than #{chiller_max_flow_rate_fraction_max * 100} % above the typical value of #{chiller_max_flow_rate_target.round(2)} #{chiller_max_flow_rate_units_ip}.")
        end
      end

      # loop through air loops to get max flor rate and cooling capacity.
      @model.getAirLoopHVACs.sort.each do |air_loop|
        # TODO: - check if DOAS, don't check airflow or cooling capacity if it is (why not check OA for DOAS? would it be different target)

        # gather argument options for air_loop_max_flow_rate checks
        air_loop_max_flow_rate_target = options['air_loop_max_flow_rate']['target']
        air_loop_max_flow_rate_fraction_min = options['air_loop_max_flow_rate']['min']
        air_loop_max_flow_rate_fraction_max = options['air_loop_max_flow_rate']['max']
        air_loop_max_flow_rate_units_ip = options['air_loop_max_flow_rate']['units']
        air_loop_max_flow_rate_units_si = 'm^3/m^2*s'

        # get values from model for air loop checks
        if use_old_gem_code
          floor_area_served = air_loop.floor_area_served # m^2
        else
          floor_area_served = std.air_loop_hvac_floor_area_served(air_loop) # m^2
        end

        if use_old_gem_code
          design_supply_air_flow_rate = air_loop.find_design_supply_air_flow_rate # m^3/s
        else
          design_supply_air_flow_rate = std.air_loop_hvac_find_design_supply_air_flow_rate(air_loop) # m^3/s
        end

        # check max flow rate of air loops in the model
        model_normalized_flow_rate_si = design_supply_air_flow_rate / floor_area_served
        model_normalized_flow_rate_ip = OpenStudio.convert(model_normalized_flow_rate_si, air_loop_max_flow_rate_units_si, air_loop_max_flow_rate_units_ip).get
        if model_normalized_flow_rate_ip < air_loop_max_flow_rate_target * (1.0 - air_loop_max_flow_rate_fraction_min)
          check_elems <<  OpenStudio::Attribute.new('flag', "Flow Rate of #{model_normalized_flow_rate_ip.round(2)} #{air_loop_max_flow_rate_units_ip} for #{air_loop.name.get} is more than #{air_loop_max_flow_rate_fraction_min * 100} % below the typical value of #{air_loop_max_flow_rate_target.round(2)} #{air_loop_max_flow_rate_units_ip}.")
        elsif model_normalized_flow_rate_ip > air_loop_max_flow_rate_target * (1.0 + air_loop_max_flow_rate_fraction_max)
          check_elems <<  OpenStudio::Attribute.new('flag', "Flow Rate of #{model_normalized_flow_rate_ip.round(2)} #{air_loop_max_flow_rate_units_ip} for #{air_loop.name.get} is more than #{air_loop_max_flow_rate_fraction_max * 100} % above the typical value of #{air_loop_max_flow_rate_target.round(2)} #{air_loop_max_flow_rate_units_ip}.")
        end
      end

      # loop through air loops to get max flor rate and cooling capacity.
      @model.getAirLoopHVACs.sort.each do |air_loop|
        # check if DOAS, don't check airflow or cooling capacity if it is
        sizing_system = air_loop.sizingSystem
        next if sizing_system.typeofLoadtoSizeOn.to_s == 'VentilationRequirement'

        # gather argument options for air_loop_cooling_capacity checks
        air_loop_cooling_capacity_target = options['air_loop_cooling_capacity']['target']
        air_loop_cooling_capacity_fraction_min = options['air_loop_cooling_capacity']['min']
        air_loop_cooling_capacity_fraction_max = options['air_loop_cooling_capacity']['max']
        air_loop_cooling_capacity_units_ip = options['air_loop_cooling_capacity']['units'] # tons/ft^2
        # string above or display only, for converstion 12000 Btu/h per ton
        air_loop_cooling_capacity_units_si = 'W/m^2'

        # get values from model for air loop checks
        if use_old_gem_code
          floor_area_served = air_loop.floor_area_served # m^2
        else
          floor_area_served = std.air_loop_hvac_floor_area_served(air_loop) # m^2
        end

        if use_old_gem_code
          capacity = air_loop.total_cooling_capacity # W
        else
          capacity = std.air_loop_hvac_total_cooling_capacity(air_loop) # W
        end

        # check cooling capacity of air loops in the model
        model_normalized_capacity_si = capacity / floor_area_served
        model_normalized_capacity_ip = OpenStudio.convert(model_normalized_capacity_si, air_loop_cooling_capacity_units_si, 'Btu/ft^2*h').get / 12000.0 # hard coded to get tons from Btu/h

        # want to display in tons/ft^2 so invert number and display for checks
        model_tons_per_area_ip = 1.0 / model_normalized_capacity_ip
        target_tons_per_area_ip = 1.0 / air_loop_cooling_capacity_target
        inverted_units = 'ft^2/ton'

        if model_tons_per_area_ip < target_tons_per_area_ip * (1.0 - air_loop_cooling_capacity_fraction_max)
          check_elems <<  OpenStudio::Attribute.new('flag', "Cooling Capacity of #{model_tons_per_area_ip.round} #{inverted_units} for #{air_loop.name.get} is more than #{air_loop_cooling_capacity_fraction_max * 100} % below the typical value of #{target_tons_per_area_ip.round} #{inverted_units}.")
        elsif model_tons_per_area_ip > target_tons_per_area_ip * (1.0 + air_loop_cooling_capacity_fraction_min)
          check_elems <<  OpenStudio::Attribute.new('flag', "Cooling Capacity of #{model_tons_per_area_ip.round} #{inverted_units} for #{air_loop.name.get} is more than #{air_loop_cooling_capacity_fraction_min * 100} % above the typical value of #{target_tons_per_area_ip.round} #{inverted_units}.")
        end
      end

      # check heating capacity of thermal zones in the model with exterior exposure
      report_name = 'HVACSizingSummary'
      table_name = 'Zone Sensible Heating'
      column_name = 'User Design Load per Area'
      target = options['zone_heating_capacity']['target']
      fraction_min = options['zone_heating_capacity']['min']
      fraction_max = options['zone_heating_capacity']['max']
      units_ip = options['zone_heating_capacity']['units']
      units_si = 'W/m^2'
      @model.getThermalZones.sort.each do |thermal_zone|
        next if thermal_zone.canBePlenum
        next if thermal_zone.exteriorSurfaceArea == 0.0
        query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{thermal_zone.name.get.upcase}' and ColumnName= '#{column_name}'"
        results = @sql.execAndReturnFirstDouble(query) # W/m^2
        model_zone_heating_capacity_ip = OpenStudio.convert(results.to_f, units_si, units_ip).get
        # check actual against target
        if model_zone_heating_capacity_ip < target * (1.0 - fraction_min)
          check_elems <<  OpenStudio::Attribute.new('flag', "Heating Capacity of #{model_zone_heating_capacity_ip.round(2)} Btu/ft^2*h for #{thermal_zone.name.get} is more than #{fraction_min * 100} % below the typical value of #{target.round(2)} Btu/ft^2*h.")
        elsif model_zone_heating_capacity_ip > target * (1.0 + fraction_max)
          check_elems <<  OpenStudio::Attribute.new('flag', "Heating Capacity of #{model_zone_heating_capacity_ip.round(2)} Btu/ft^2*h for #{thermal_zone.name.get} is more than #{fraction_max * 100} % above the typical value of #{target.round(2)} Btu/ft^2*h.")
        end
      end
    rescue StandardError => e
      # brief description of ruby error
      check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")

      # backtrace of ruby error for diagnostic use
      if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
    end

    # add check_elms to new attribute
    check_elem = OpenStudio::Attribute.new('check', check_elems)

    return check_elem
    # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb
  end
end