# *******************************************************************************
# OpenStudio(R), Copyright (c) 2008-2019, 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.
# *******************************************************************************

module OsLib_Cofee
  # create def to use later to make bar
  def self.createBar(model, spaceTypeHash, lengthXTarget, lengthYTarget, totalFloorArea, numStories, midFloorMultiplier, xmin, ymin, lengthX, lengthY, zmin, zmax, endZones)
    # floor to floor height
    floor_to_floor_height = (zmax - zmin) / numStories

    # perimeter depth
    perimeterDepth = OpenStudio.convert(12, 'ft', 'm').get
    perimeterBufferFactor = 1.5 # this is a margin below which I won't bother splitting the two largest spaces

    # create an array to control sort order of spaces in bar
    customSpaceTypeBar = []
    counter = 0
    spaceTypeHash.sort_by { |key, value| value }.reverse.each do |k, v|
      next if v == 0 # this line adds support for fractional values of 0
      if counter == 1
        if (lengthXTarget * (v / totalFloorArea) > perimeterDepth * perimeterBufferFactor) && endZones
          customSpaceTypeBar << [k, totalFloorArea * (perimeterDepth / lengthXTarget)]
          customSpaceTypeBar << [k, v - (totalFloorArea * (perimeterDepth / lengthXTarget))]
        else
          customSpaceTypeBar << [k, v]
        end
      elsif counter > 1
        customSpaceTypeBar << [k, v]
      end
      counter += 1
    end

    # add the largest space type to the end
    counter = 0
    spaceTypeHash.sort_by { |key, value| value }.reverse.each do |k, v|
      if counter == 0
        # if width is greater than 1.5x perimeter depth then split in half
        if (lengthXTarget * (v / totalFloorArea) > perimeterDepth * perimeterBufferFactor) && endZones
          customSpaceTypeBar << [k, v - (totalFloorArea * (perimeterDepth / lengthXTarget))]
          customSpaceTypeBar << [k, totalFloorArea * (perimeterDepth / lengthXTarget)]
        else
          customSpaceTypeBar << [k, v]
        end
      end
      break
    end

    # starting z level
    z = zmin
    storyCounter = 0
    barSpaceArray = []

    # create new stories and then add spaces
    [numStories, 3].min.times do # no more than tree loops through this
      story = OpenStudio::Model::BuildingStory.new(model)
      story.setNominalFloortoFloorHeight(floor_to_floor_height)
      story.setNominalZCoordinate(z)

      # starting position for first space
      x = (lengthX - lengthXTarget) * 0.5 + xmin
      y = (lengthY - lengthYTarget) * 0.5 + ymin

      # temp array of spaces (this is to change floor boundary when there is mid floor multiplier)
      tempSpaceArray = []

      # loop through space types making diagram and spaces.
      # spaceTypeHash.sort_by {|key, value| value}.reverse.each do |k,v|
      customSpaceTypeBar.each do |object|
        # get values from what was hash
        k = object[0]
        v = object[1]

        # get proper zone multiplier value
        if (storyCounter == 1) && (midFloorMultiplier > 1)
          thermalZoneMultiplier = midFloorMultiplier
        else
          thermalZoneMultiplier = 1
        end

        options = {
          'name' => nil,
          'spaceType' => k,
          'story' => story,
          'makeThermalZone' => true,
          'thermalZone' => nil,
          'thermalZoneMultiplier' => thermalZoneMultiplier,
          'floor_to_floor_height' => floor_to_floor_height
        }

        # three paths for spaces depending upon building depth (3, 2 or one cross slices)
        if lengthYTarget > perimeterDepth * 3 # slice into core and perimeter

          # perimeter polygon a
          perim_polygon_a = OpenStudio::Point3dVector.new
          perim_origin_a = OpenStudio::Point3d.new(x, y, z)
          perim_polygon_a << perim_origin_a
          perim_polygon_a << OpenStudio::Point3d.new(x, y + perimeterDepth, z)
          perim_polygon_a << OpenStudio::Point3d.new(x + lengthXTarget * (v / totalFloorArea), y + perimeterDepth, z)
          perim_polygon_a << OpenStudio::Point3d.new(x + lengthXTarget * (v / totalFloorArea), y, z)

          # create core polygon
          core_polygon = OpenStudio::Point3dVector.new
          core_origin = OpenStudio::Point3d.new(x, y + perimeterDepth, z)
          core_polygon << core_origin
          core_polygon << OpenStudio::Point3d.new(x, y + lengthYTarget - perimeterDepth, z)
          core_polygon << OpenStudio::Point3d.new(x + lengthXTarget * (v / totalFloorArea), y + lengthYTarget - perimeterDepth, z)
          core_polygon << OpenStudio::Point3d.new(x + lengthXTarget * (v / totalFloorArea), y + perimeterDepth, z)

          # perimeter polygon b                              w
          perim_polygon_b = OpenStudio::Point3dVector.new
          perim_origin_b = OpenStudio::Point3d.new(x, y + lengthYTarget - perimeterDepth, z)
          perim_polygon_b << perim_origin_b
          perim_polygon_b << OpenStudio::Point3d.new(x, y + lengthYTarget, z)
          perim_polygon_b << OpenStudio::Point3d.new(x + lengthXTarget * (v / totalFloorArea), y + lengthYTarget, z)
          perim_polygon_b << OpenStudio::Point3d.new(x + lengthXTarget * (v / totalFloorArea), y + lengthYTarget - perimeterDepth, z)

          # run method to make spaces
          tempSpaceArray << OsLib_Geometry.makeSpaceFromPolygon(model, perim_origin_a, perim_polygon_a, options) # model, origin, polygon, options
          tempSpaceArray << OsLib_Geometry.makeSpaceFromPolygon(model, core_origin, core_polygon, options) # model, origin, polygon, options
          tempSpaceArray << OsLib_Geometry.makeSpaceFromPolygon(model, perim_origin_b, perim_polygon_b, options) # model, origin, polygon, options

        elsif lengthYTarget > perimeterDepth * 2 # slice into two peremeter zones but no core

          # perimeter polygon a
          perim_polygon_a = OpenStudio::Point3dVector.new
          perim_origin_a = OpenStudio::Point3d.new(x, y, z)
          perim_polygon_a << perim_origin_a
          perim_polygon_a << OpenStudio::Point3d.new(x, y + lengthYTarget / 2, z)
          perim_polygon_a << OpenStudio::Point3d.new(x + lengthXTarget * (v / totalFloorArea), y + lengthYTarget / 2, z)
          perim_polygon_a << OpenStudio::Point3d.new(x + lengthXTarget * (v / totalFloorArea), y, z)

          # perimeter polygon b
          perim_polygon_b = OpenStudio::Point3dVector.new
          perim_origin_b = OpenStudio::Point3d.new(x, y + lengthYTarget / 2, z)
          perim_polygon_b << perim_origin_b
          perim_polygon_b << OpenStudio::Point3d.new(x, y + lengthYTarget, z)
          perim_polygon_b << OpenStudio::Point3d.new(x + lengthXTarget * (v / totalFloorArea), y + lengthYTarget, z)
          perim_polygon_b << OpenStudio::Point3d.new(x + lengthXTarget * (v / totalFloorArea), y + lengthYTarget / 2, z)

          # run method to make spaces
          tempSpaceArray << OsLib_Geometry.makeSpaceFromPolygon(model, perim_origin_a, perim_polygon_a, options) # model, origin, polygon, options
          tempSpaceArray << OsLib_Geometry.makeSpaceFromPolygon(model, perim_origin_b, perim_polygon_b, options) # model, origin, polygon, options

        else # don't slice into core and perimeter

          # create polygon
          core_polygon = OpenStudio::Point3dVector.new
          core_origin = OpenStudio::Point3d.new(x, y, z)
          core_polygon << core_origin
          core_polygon << OpenStudio::Point3d.new(x, y + lengthYTarget, z)
          core_polygon << OpenStudio::Point3d.new(x + lengthXTarget * (v / totalFloorArea), y + lengthYTarget, z)
          core_polygon << OpenStudio::Point3d.new(x + lengthXTarget * (v / totalFloorArea), y, z)

          # run method to make space
          tempSpaceArray << OsLib_Geometry.makeSpaceFromPolygon(model, core_origin, core_polygon, options) # model, origin, polygon, options

        end

        # update points for next run
        x += lengthXTarget * (v / totalFloorArea)
      end

      # set flags for adiabatic surfaces
      floorAdiabatic = false
      ceilingAdiabatic = false

      # update z
      if midFloorMultiplier == 1
        z += floor_to_floor_height
      else
        z += floor_to_floor_height * midFloorMultiplier - floor_to_floor_height

        if storyCounter == 0
          ceilingAdiabatic = true
        elsif storyCounter == 1
          floorAdiabatic = true
          ceilingAdiabatic = true
        else
          floorAdiabatic = true
        end

        # alter surfaces boundary conditions and constructions as described above
        tempSpaceArray.each do |space|
          space.surfaces.each do |surface|
            if (surface.surfaceType == 'RoofCeiling') && ceilingAdiabatic
              construction = surface.construction # TODO: - this isn't really the construction I want since it wasn't an interior one, but will work for now
              surface.setOutsideBoundaryCondition('Adiabatic')
              if !construction.empty?
                surface.setConstruction(construction.get)
              end
            end
            if (surface.surfaceType == 'Floor') && floorAdiabatic
              construction = surface.construction # TODO: - this isn't really the construction I want since it wasn't an interior one, but will work for now
              surface.setOutsideBoundaryCondition('Adiabatic')
              if !construction.empty?
                surface.setConstruction(construction.get)
              end
            end
          end
        end

        # populate bar space array from temp array
        barSpaceArray << tempSpaceArray

      end

      # update storyCounter
      storyCounter += 1
    end

    # surface matching (seems more complex than necessary)
    spaces = OpenStudio::Model::SpaceVector.new
    model.getSpaces.each do |space|
      spaces << space
    end
    OpenStudio::Model.matchSurfaces(spaces)

    result = barSpaceArray
    return result
  end
end