# frozen_string_literal: true

# *********************************************************************************
# URBANopt™, Copyright (c) 2019-2021, Alliance for Sustainable Energy, 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:

# Redistributions of source code must retain the above copyright notice, this list
# of conditions and the following disclaimer.

# 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.

# Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.

# Redistribution of this software, without modification, must refer to the software
# by the same designation. Redistribution of a modified version of this software
# (i) may not refer to the modified version by the same designation, or by any
# confusingly similar designation, and (ii) must refer to the underlying software
# originally provided by Alliance as “URBANopt”. Except to comply with the foregoing,
# the term “URBANopt”, or any confusingly similar designation may not be used to
# refer to any modified version of this software or any modified version of the
# underlying software originally provided by Alliance without the prior written
# consent of Alliance.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 OR CONTRIBUTORS 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 'json'
require 'net/http'
require 'uri'
require 'urbanopt/geojson'

# start the measure
class UrbanGeometryCreation < OpenStudio::Measure::ModelMeasure
  attr_accessor :origin_lat_lon

  # human readable name
  def name
    return 'UrbanGeometryCreation'
  end

  # human readable description
  def description
    return 'This measure reads an URBANopt GeoJSON and creates geometry for a particular building.  Surrounding buildings are included as shading structures.'
  end

  # human readable description of modeling approach
  def modeler_description
    return 'This measure takes in the GeoJSON file, the feature_id of the building and the surrounding buildings as arguments and add has methods to create space types and add default construction sets.'
  end

  def arguments(model)
    args = OpenStudio::Measure::OSArgumentVector.new
    # geojson file
    geojson_file = OpenStudio::Measure::OSArgument.makeStringArgument('geojson_file', true)
    geojson_file.setDisplayName('GeoJSON File')
    geojson_file.setDescription('GeoJSON File.')
    args << geojson_file
    # feature id of the building to create
    feature_id = OpenStudio::Measure::OSArgument.makeStringArgument('feature_id', true)
    feature_id.setDisplayName('Feature ID')
    feature_id.setDescription('Feature ID.')
    args << feature_id
    # which surrounding buildings to include
    chs = OpenStudio::StringVector.new
    chs << 'None'
    chs << 'ShadingOnly'
    surrounding_buildings = OpenStudio::Measure::OSArgument.makeChoiceArgument('surrounding_buildings', chs, true)
    surrounding_buildings.setDisplayName('Surrounding Buildings')
    surrounding_buildings.setDescription('Select which surrounding buildings to include.')
    surrounding_buildings.setDefaultValue('ShadingOnly')
    args << surrounding_buildings
    # not a required argument
    scale_footprint_area_by_floor_area = OpenStudio::Ruleset::OSArgument.makeBoolArgument('scale_footprint_area_by_floor_area', false)
    scale_footprint_area_by_floor_area.setDisplayName('Scale Footprint Area by the Floor Area?')
    scale_footprint_area_by_floor_area.setDescription('If true, the footprint area from GeoJSON will be scaled by the floor_area provided by the user in URBANopt.')
    scale_footprint_area_by_floor_area.setDefaultValue(false)
    args << scale_footprint_area_by_floor_area
    return args
  end

  # define what happens when the measure is run
  def run(model, runner, user_arguments)
    super(model, runner, user_arguments)
    # use the built-in error checking
    if !runner.validateUserArguments(arguments(model), user_arguments)
      return false
    end

    # assign the user inputs to variables
    geojson_file = runner.getStringArgumentValue('geojson_file', user_arguments)
    feature_id = runner.getStringArgumentValue('feature_id', user_arguments)
    surrounding_buildings = runner.getStringArgumentValue('surrounding_buildings', user_arguments)
    scale_footprint_area_by_floor_area = runner.getBoolArgumentValue('scale_footprint_area_by_floor_area', user_arguments)

    default_construction_set = URBANopt::GeoJSON::Model.create_construction_set(model, runner)

    stories = []
    model.getBuildingStorys.each { |story| stories << story }
    stories.sort! { |x, y| x.nominalZCoordinate.to_s.to_f <=> y.nominalZCoordinate.to_s.to_f }

    space_types = URBANopt::GeoJSON::Helper.create_space_types(stories, model, runner)

    # delete the previous building
    model.getBuilding.remove

    # create new building and transfer default construction set
    model.getBuilding.setDefaultConstructionSet(default_construction_set)

    # instance variables
    @runner = runner
    @origin_lat_lon = nil

    all_features = URBANopt::GeoJSON::GeoFile.from_file(geojson_file)
    feature = all_features.get_feature_by_id(feature_id)

    # EXPOSE NAME
    name = feature.feature_json[:properties][:name]
    model.getBuilding.setName(name)

    # find min and max x coordinate
    @origin_lat_lon = feature.create_origin_lat_lon(@runner)

    site = model.getSite
    site.setLatitude(@origin_lat_lon.lat)
    site.setLongitude(@origin_lat_lon.lon)

    begin
      surface_elevation = feature.surface_elevation.to_f
      surface_elevation = OpenStudio.convert(surface_elevation, 'ft', 'm').get
      site.setElevation(surface_elevation)
    rescue StandardError
      @runner.registerWarning("Surface elevation not set for building '#{name}'")
    end

    if feature.type == 'Building'
      # make requested building
      # pass in scaled_footprint_area (calculated from floor_area / number_of_stories)
      scaled_footprint_area = 0
      if scale_footprint_area_by_floor_area
        building_hash = feature.to_hash
        if building_hash[:number_of_stories] && building_hash[:floor_area]
          scaled_footprint_area = building_hash[:floor_area].to_f / building_hash[:number_of_stories].to_f
          @runner.registerInfo("Desired footprint area in ft2: #{scaled_footprint_area}")
        end
      end

      spaces = feature.create_building(:space_per_floor, model, @origin_lat_lon, @runner, false, scaled_footprint_area)
      if spaces.nil?
        @runner.registerError("Failed to create spaces for building '#{name}'")
        return false
      end

      # DLM: temp hack
      building_type = feature.building_type
      if building_type == 'Vacant'
        shading_surfaces = URBANopt::GeoJSON::Helper.create_shading_surfaces(feature, model, @origin_lat_lon, @runner, spaces)
      end

      # make other buildings to convert to shading
      convert_to_shades = []
      if surrounding_buildings == 'ShadingOnly'
        convert_to_shades = feature.create_other_buildings(surrounding_buildings, all_features.json, model, @origin_lat_lon, @runner)
      end

      # intersect surfaces in this building with others
      @runner.registerInfo('Intersecting surfaces')
      spaces.each do |space|
        convert_to_shades.each do |other_space|
          space.intersectSurfaces(other_space)
        end
      end

      # match surfaces
      @runner.registerInfo('Matching surfaces')
      all_spaces = OpenStudio::Model::SpaceVector.new
      model.getSpaces.each do |space|
        all_spaces << space
      end
      OpenStudio::Model.matchSurfaces(all_spaces)

      # make windows
      spaces = feature.create_windows(spaces)

      # change adjacent surfaces to adiabatic
      model = URBANopt::GeoJSON::Model.change_adjacent_surfaces_to_adiabatic(model, @runner)

      # convert other buildings to shading surfaces
      convert_to_shades.map do |space|
        URBANopt::GeoJSON::Helper.convert_to_shading_surface_group(space)
      end

    elsif feature.type == 'District System'
      district_system_type = feature[:properties][:district_system_type]
      if district_system_type == 'Community Photovoltaic'
        shading_surfaces = URBANopt::GeoJSON::Helper.create_photovoltaics(feature, 0, model, @origin_lat_lon, @runner)
      end
    else
      @runner.registerError("Unknown feature type '#{feature.type}'")
      return false
    end

    # transfer data from previous model
    stories = URBANopt::GeoJSON::Model.transfer_prev_model_data(model, space_types)

    return true
  end
end

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