# *******************************************************************************
# 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/geometry/aperture'

require 'to_openstudio/model_object'

module Honeybee
  class Aperture < ModelObject

    def find_existing_openstudio_object(openstudio_model)
      object = openstudio_model.getSubSurfaceByName(@hash[:identifier])
      return object.get if object.is_initialized
      nil
    end

    def to_openstudio(openstudio_model)
      # create the OpenStudio aperture object
      os_vertices = OpenStudio::Point3dVector.new
      @hash[:geometry][:boundary].each do |vertex|
        os_vertices << OpenStudio::Point3d.new(vertex[0], vertex[1], vertex[2])
      end

      # triangulate subsurface if neccesary
      triangulated = false
      final_vertices_list = []
      matching_os_subsurfaces = []
      matching_os_subsurface_indices = []
      if $triangulate_sub_faces && os_vertices.size > 4

        # if this apeture has a matched apeture, see if the other one has already been created
        # the matched apeture should have been converted to multiple subsurfaces
        if @hash[:boundary_condition][:type] == 'Surface'
          adj_srf_identifier = @hash[:boundary_condition][:boundary_condition_objects][0]
          regex = Regexp.new("#{adj_srf_identifier}\.\.(\\d+)")
          openstudio_model.getSubSurfaces.each do |subsurface|
            if md = regex.match(subsurface.nameString)
              final_vertices_list << OpenStudio.reorderULC(OpenStudio::reverse(subsurface.vertices))
              matching_os_subsurfaces << subsurface
              matching_os_subsurface_indices << md[1]
            end
          end
        end

        # if other apeture is not already created, do the triangulation
        if final_vertices_list.empty?

          # transform to face coordinates
          t = OpenStudio::Transformation::alignFace(os_vertices)
          tInv = t.inverse
          face_vertices = OpenStudio::reverse(tInv*os_vertices)

          # no holes in the subsurface
          holes = OpenStudio::Point3dVectorVector.new

          # triangulate surface
          triangles = OpenStudio::computeTriangulation(face_vertices, holes)
          if triangles.empty?
            raise "Failed to triangulate aperture #{@hash[:identifier]} with #{os_vertices.size} vertices"
          end

          # create new list of surfaces
          triangles.each do |vertices|
            final_vertices_list << OpenStudio.reorderULC(OpenStudio::reverse(t*vertices))
          end

          triangulated = true

        end

      else
        # os_vertices are good as is
        final_vertices_list << os_vertices
      end

      result = []
      final_vertices_list.each_with_index do |os_vertices, index|
        os_subsurface = OpenStudio::Model::SubSurface.new(os_vertices, openstudio_model)

        if !matching_os_subsurfaces.empty?
          os_subsurface.setName(@hash[:identifier] + "..#{matching_os_subsurface_indices[index]}")
        elsif triangulated
          os_subsurface.setName(@hash[:identifier] + "..#{index}")
        else
          os_subsurface.setName(@hash[:identifier])
        end

        unless @hash[:display_name].nil?
          os_subsurface.setDisplayName(@hash[:display_name])
        end

        # assign the construction if it exists
        if @hash[:properties].key?(:energy) && @hash[:properties][:energy][:construction]
          construction_identifier = @hash[:properties][:energy][:construction]
          construction = openstudio_model.getConstructionByName(construction_identifier)
          if !construction.empty?
            os_construction = construction.get
            os_subsurface.setConstruction(os_construction)
          elsif $window_dynamic_hash[construction_identifier] != nil
            os_construction = $window_dynamic_hash[construction_identifier].constructions[0]
            os_subsurface.setConstruction(os_construction)
          end
        end

        # assign the boundary condition object if it's a Surface
        if @hash[:boundary_condition][:type] == 'Surface'
          if !matching_os_subsurfaces.empty?
            # we already have the match because this was created from the matching_os_subsurfaces
            # setAdjacentSubSurface will fail at this point because sub surface is not assigned to surface yet, store data for later
            adj_srf_identifier = matching_os_subsurfaces[index].nameString
            os_subsurface.additionalProperties.setFeature("AdjacentSubSurfaceName", adj_srf_identifier)
          elsif triangulated
            # other subsurfaces haven't been created yet, no-op
          else
            # get adjacent sub surface by identifier from openstudio model
            # setAdjacentSubSurface will fail at this point because sub surface is not assigned to surface yet, store data for later
            adj_srf_identifier = @hash[:boundary_condition][:boundary_condition_objects][0]
            os_subsurface.additionalProperties.setFeature("AdjacentSubSurfaceName", adj_srf_identifier)
          end
        end

        # assign the operable property
        if @hash[:is_operable] == false
          os_subsurface.setSubSurfaceType('FixedWindow')
        else
          os_subsurface.setSubSurfaceType('OperableWindow')
        end

        result << os_subsurface
      end

      return result
    end

    def to_openstudio_shade(openstudio_model, shading_surface_group)
      # get the vertices from the aperture
      if @hash[:geometry][:vertices].nil?
        hb_verts = @hash[:geometry][:boundary]
      else
        hb_verts = @hash[:geometry][:vertices]
      end

      # create the openstudio shading surface
      os_vertices = OpenStudio::Point3dVector.new
      hb_verts.each do |vertex|
        os_vertices << OpenStudio::Point3d.new(vertex[0], vertex[1], vertex[2])
      end

      os_shading_surface = OpenStudio::Model::ShadingSurface.new(os_vertices, openstudio_model)
      os_shading_surface.setName(@hash[:identifier])
      unless @hash[:display_name].nil?
        os_shading_surface.setDisplayName(@hash[:display_name])
      end

      # get the approriate construction id
      construction_id = nil
      if @hash[:properties].key?(:energy) && @hash[:properties][:energy][:construction]
        construction_id = @hash[:properties][:energy][:construction]
      else
        construction_id = 'Generic Double Pane'
      end
  
      # assign the construction
      unless construction_id.nil?
        construction = openstudio_model.getConstructionByName(construction_id)
        unless construction.empty?
          os_construction = construction.get
          os_shading_surface.setConstruction(os_construction)
        end
      end

      # add a transmittance schedule if a value is found
      if @hash[:transmit]
        schedule_identifier = 'Constant ' + @hash[:transmit] + ' Transmittance'
        schedule = openstudio_model.getScheduleByName(schedule_identifier)
        unless schedule.empty?
          os_schedule = schedule.get
          os_shading_surface.setTransmittanceSchedule(os_schedule)
        end
      end

      # add the shade to the group
      os_shading_surface.setShadingSurfaceGroup(shading_surface_group)

      os_shading_surface
    end
  
  end # Aperture
end # Honeybee