# *******************************************************************************
# OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC.
# 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.
#
# (4) Other than as required in clauses (1) and (2), distributions in any form
# of modifications or other derivative works may not use the "OpenStudio"
# trademark, "OS", "os", or any other confusingly similar designation without
# specific prior written permission from Alliance for Sustainable Energy, LLC.
#
# 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.
# *******************************************************************************

# see the URL below for information on how to write OpenStudio measures
# http://nrel.github.io/OpenStudio-user-documentation/measures/measure_writing_guide/

require "#{File.dirname(__FILE__)}/resources/os_lib_helper_methods"

# start the measure
class TariffSelectionBlock < OpenStudio::Measure::EnergyPlusMeasure
  # human readable name
  def name
    return ' Tariff Selection-Block'
  end

  # human readable description
  def description
    return 'This measure sets block rates for electricity, and flat rates for gas, water, district heating, and district cooling.'
  end

  # human readable description of modeling approach
  def modeler_description
    return 'Will add the necessary UtilityCost objects into the model.'
  end

  # define the arguments that the user will input
  def arguments(workspace)
    args = OpenStudio::Measure::OSArgumentVector.new

    # make choice argument for facade
    choices = OpenStudio::StringVector.new
    choices << 'QuarterHour'
    choices << 'HalfHour'
    choices << 'FullHour'
    # don't want to offer Day or Week even though valid E+ options
    # choices << "Day"
    # choices << "Week"
    demand_window_length = OpenStudio::Measure::OSArgument.makeChoiceArgument('demand_window_length', choices, true)
    demand_window_length.setDisplayName('Demand Window Length.')
    demand_window_length.setDefaultValue('QuarterHour')
    args << demand_window_length

    # adding argument for elec_rate
    elec_block_values = OpenStudio::Measure::OSArgument.makeStringArgument('elec_block_values', true)
    elec_block_values.setDisplayName('Electric Block Rate Ceiling Values')
    elec_block_values.setDescription('Comma separated block ceilings.')
    elec_block_values.setUnits('kWh')
    elec_block_values.setDefaultValue('200,1000')
    args << elec_block_values

    # adding argument for elec_rate
    elec_block_costs = OpenStudio::Measure::OSArgument.makeStringArgument('elec_block_costs', true)
    elec_block_costs.setDisplayName('Electric Block Rate Costs')
    elec_block_costs.setDescription('Comma separated block rate values. Should have same number of rates as blocks.')
    elec_block_costs.setUnits('$/kWh')
    elec_block_costs.setDefaultValue('0.07,0.06')
    args << elec_block_costs

    # adding argument for elec_rate
    elec_remaining_rate = OpenStudio::Measure::OSArgument.makeDoubleArgument('elec_remaining_rate', true)
    elec_remaining_rate.setDisplayName('Electric Rate for Remaining')
    elec_remaining_rate.setDescription('Rate for Electricity above last block level.')
    elec_remaining_rate.setUnits('$/kWh')
    elec_remaining_rate.setDefaultValue(0.05)
    args << elec_remaining_rate

    # adding argument for gas_rate
    gas_rate = OpenStudio::Measure::OSArgument.makeDoubleArgument('gas_rate', true)
    gas_rate.setDisplayName('Gas Rate')
    gas_rate.setUnits('$/therm')
    gas_rate.setDefaultValue(0.5)
    args << gas_rate

    # adding argument for water_rate
    water_rate = OpenStudio::Measure::OSArgument.makeDoubleArgument('water_rate', true)
    water_rate.setDisplayName('Water Rate')
    water_rate.setUnits('$/gal')
    water_rate.setDefaultValue(0.005)
    args << water_rate

    # adding argument for disthtg_rate
    disthtg_rate = OpenStudio::Measure::OSArgument.makeDoubleArgument('disthtg_rate', true)
    disthtg_rate.setDisplayName('District Heating Rate')
    disthtg_rate.setUnits('$/therm')
    disthtg_rate.setDefaultValue(0.2)
    args << disthtg_rate

    # adding argument for distclg_rate
    distclg_rate = OpenStudio::Measure::OSArgument.makeDoubleArgument('distclg_rate', true)
    distclg_rate.setDisplayName('District Cooling Rate')
    distclg_rate.setUnits('$/therm')
    distclg_rate.setDefaultValue(0.2)
    args << distclg_rate

    return args
  end

  # define what happens when the measure is run
  def run(workspace, runner, user_arguments)
    super(workspace, runner, user_arguments)

    # assign the user inputs to variables
    args = OsLib_HelperMethods.createRunVariables(runner, workspace, user_arguments, arguments(workspace))
    if !args then return false end

    # make arrays out of comma separated string inputs
    block_size_array = args['elec_block_values'].split(',')
    block_rate_array = args['elec_block_costs'].split(',')

    # throw error if size of blocks doesn't match size of rates
    if block_size_array.size != block_rate_array.size
      runner.registerError('The number of block rates should match the number of block sizes. This is excluding the renaming rate above the last block value.')
      return false
    end

    # throw error if block sizes don't get increasingly larger
    block_size = 0.0
    block_size_array.each do |block|
      if block.to_f <= block_size.to_f
        runner.registerError('Each block size should increase in size and be greater than 0.')
        return false
      else
        block_size = block
      end
    end

    # TODO: - throw error if block rates can't be converted to doubles

    # reporting initial condition of model
    starting_tariffs = workspace.getObjectsByType('UtilityCost:Tariff'.to_IddObjectType)
    runner.registerInitialCondition("The model started with #{starting_tariffs.size} tariff objects.")

    # map demand window length to integer
    demand_window_per_hour = nil
    if args['demand_window_length'] == 'QuarterHour'
      demand_window_per_hour = 4
    elsif args['demand_window_length'] == 'HalfHour'
      demand_window_per_hour = 2
    elsif args['demand_window_length'] == 'FullHour'
      demand_window_per_hour = 1
    end

    # make sure demand window length is is divisible by timestep
    if !workspace.getObjectsByType('Timestep'.to_IddObjectType).empty?
      initial_timestep = workspace.getObjectsByType('Timestep'.to_IddObjectType)[0].getString(0).get

      if initial_timestep.to_f / demand_window_per_hour.to_f == (initial_timestep.to_f / demand_window_per_hour.to_f).truncate # checks if remainder of divided numbers is > 0
        runner.registerInfo("The demand window length of a #{args['demand_window_length']} is compatible with the current setting of #{initial_timestep} timesteps per hour.")
      else
        workspace.getObjectsByType('Timestep'.to_IddObjectType)[0].setString(0, demand_window_per_hour.to_s)
        runner.registerInfo("Updating the timesteps per hour in the model from #{initial_timestep} to #{demand_window_per_hour} to be compatible with the demand window length of a #{args['demand_window_length']}")
      end
    else

      # add a timestep object to the workspace
      new_object_string = "
      Timestep,
        4;                                      !- Number of Timesteps per Hour
        "
      idfObject = OpenStudio::IdfObject.load(new_object_string)
      object = idfObject.get
      wsObject = workspace.addObject(object)
      new_object = wsObject.get
      runner.registerInfo('No timestep object found. Added a new timestep object set to 4 timesteps per hour')
    end

    # elec tariff object
    new_object_string = "
    UtilityCost:Tariff,
      Electricity Tariff,                     !- Name
      ElectricityPurchased:Facility,          !- Output Meter Name
      kWh,                                    !- Conversion Factor Choice
      ,                                       !- Energy Conversion Factor
      ,                                       !- Demand Conversion Factor
      ,                                       !- Time of Use Period Schedule Name
      ,                                       !- Season Schedule Name
      ,                                       !- Month Schedule Name
      Day,                                    !- Demand Window Length
      0.0;                                    !- Monthly Charge or Variable Name
      "
    elec_tariff = workspace.addObject(OpenStudio::IdfObject.load(new_object_string).get).get

    # make UtilityCost:Charge:Block object
    new_object_array = []
    new_object_array << "
    UtilityCost:Charge:Block,
      BlockEnergyCharge,            ! Charge Variable Name
      Electricity Tariff,           ! Tariff Name
      totalEnergy,                  ! Source Variable
      Annual,                       ! Season
      EnergyCharges,                ! Category Variable Name
      ,                             ! Remaining Into Variable
      ,                             ! Block Size Multiplier Value or Variable Name
      "

    # loop through blocks to extend array for new_object_string
    block_counter = 0
    block_size_array.each do |block_size|
      new_object_array << "
      #{block_size},                        ! Block Size #{block_counter + 1} Value or Variable Name
      #{block_rate_array[block_counter]},   ! Block #{block_counter + 1} Cost per Unit Value or Variable Name
      "
      block_counter += 1
    end

    new_object_array << "
      remaining,                        ! Block Size #{block_counter + 1} Value or Variable Name
      #{args['elec_remaining_rate']};   ! Block #{block_counter + 1} Cost per Unit Value or Variable Name
      "

    new_object_string = new_object_array.join('')
    elec_utility_cost = workspace.addObject(OpenStudio::IdfObject.load(new_object_string).get).get

    # gas tariff object
    if args['gas_rate'] > 0
      new_object_string = "
      UtilityCost:Tariff,
        Gas Tariff,                             !- Name
        Gas:Facility,                           !- Output Meter Name
        Therm,                                  !- Conversion Factor Choice
        ,                                       !- Energy Conversion Factor
        ,                                       !- Demand Conversion Factor
        ,                                       !- Time of Use Period Schedule Name
        ,                                       !- Season Schedule Name
        ,                                       !- Month Schedule Name
        Day,                                    !- Demand Window Length
        0.0;                                    !- Monthly Charge or Variable Name
        "
      gas_tariff = workspace.addObject(OpenStudio::IdfObject.load(new_object_string).get).get

      # make UtilityCost:Charge:Simple objects for gas
      new_object_string = "
      UtilityCost:Charge:Simple,
        GasTariffEnergyCharge, !- Name
        Gas Tariff,                             !- Tariff Name
        totalEnergy,                            !- Source Variable
        Annual,                                 !- Season
        EnergyCharges,                          !- Category Variable Name
        #{args['gas_rate']};          !- Cost per Unit Value or Variable Name
        "
      gas_utility_cost = workspace.addObject(OpenStudio::IdfObject.load(new_object_string).get).get
    end

    # conversion for water tariff rate
    dollars_per_gallon = args['water_rate']
    dollars_per_meter_cubed = OpenStudio.convert(dollars_per_gallon, '1/gal', '1/m^3').get

    # water tariff object
    if args['water_rate'] > 0
      new_object_string = "
      UtilityCost:Tariff,
        Water Tariff,                             !- Name
        Water:Facility,             !- Output Meter Name
        UserDefined,                            !- Conversion Factor Choice
        1,                                       !- Energy Conversion Factor
        ,                                       !- Demand Conversion Factor
        ,                                       !- Time of Use Period Schedule Name
        ,                                       !- Season Schedule Name
        ,                                       !- Month Schedule Name
        ,                                       !- Demand Window Length
        0.0;                                    !- Monthly Charge or Variable Name
        "
      water_tariff = workspace.addObject(OpenStudio::IdfObject.load(new_object_string).get).get

      # make UtilityCost:Charge:Simple objects for water
      new_object_string = "
      UtilityCost:Charge:Simple,
        WaterTariffEnergyCharge, !- Name
        Water Tariff,                             !- Tariff Name
        totalEnergy,                             !- Source Variable
        Annual,                                 !- Season
        EnergyCharges,                          !- Category Variable Name
        #{dollars_per_meter_cubed};          !- Cost per Unit Value or Variable Name
        "
      water_utility_cost = workspace.addObject(OpenStudio::IdfObject.load(new_object_string).get).get
    end

    # disthtg tariff object
    if args['disthtg_rate'] > 0
      new_object_string = "
      UtilityCost:Tariff,
        DistrictHeating Tariff,                             !- Name
        DistrictHeating:Facility,                           !- Output Meter Name
        Therm,                                  !- Conversion Factor Choice
        ,                                       !- Energy Conversion Factor
        ,                                       !- Demand Conversion Factor
        ,                                       !- Time of Use Period Schedule Name
        ,                                       !- Season Schedule Name
        ,                                       !- Month Schedule Name
        Day,                                    !- Demand Window Length
        0.0;                                    !- Monthly Charge or Variable Name
        "
      disthtg_tariff = workspace.addObject(OpenStudio::IdfObject.load(new_object_string).get).get

      # make UtilityCost:Charge:Simple objects for disthtg
      new_object_string = "
      UtilityCost:Charge:Simple,
        DistrictHeatingTariffEnergyCharge, !- Name
        DistrictHeating Tariff,                             !- Tariff Name
        totalEnergy,                            !- Source Variable
        Annual,                                 !- Season
        EnergyCharges,                          !- Category Variable Name
        #{args['disthtg_rate']};          !- Cost per Unit Value or Variable Name
        "
      disthtg_utility_cost = workspace.addObject(OpenStudio::IdfObject.load(new_object_string).get).get
    end

    # distclg tariff object
    if args['distclg_rate'] > 0
      new_object_string = "
      UtilityCost:Tariff,
        DistrictCooling Tariff,                             !- Name
        DistrictCooling:Facility,                           !- Output Meter Name
        Therm,                                  !- Conversion Factor Choice
        ,                                       !- Energy Conversion Factor
        ,                                       !- Demand Conversion Factor
        ,                                       !- Time of Use Period Schedule Name
        ,                                       !- Season Schedule Name
        ,                                       !- Month Schedule Name
        Day,                                    !- Demand Window Length
        0.0;                                    !- Monthly Charge or Variable Name
        "
      distclg_tariff = workspace.addObject(OpenStudio::IdfObject.load(new_object_string).get).get

      # make UtilityCost:Charge:Simple objects for distclg
      new_object_string = "
      UtilityCost:Charge:Simple,
        DistrictCoolingTariffEnergyCharge, !- Name
        DistrictCooling Tariff,                             !- Tariff Name
        totalEnergy,                            !- Source Variable
        Annual,                                 !- Season
        EnergyCharges,                          !- Category Variable Name
        ['distclg_rate'];          !- Cost per Unit Value or Variable Name
      "
      distclg_utility_cost = workspace.addObject(OpenStudio::IdfObject.load(new_object_string).get).get
    end

    # report final condition of model
    finishing_tariffs = workspace.getObjectsByType('UtilityCost:Tariff'.to_IddObjectType)
    runner.registerFinalCondition("The model finished with #{finishing_tariffs.size} tariff objects.")

    return true
  end
end

# register the measure to be used by the application
TariffSelectionBlock.new.registerWithApplication