lib/seatsio/domain.rb



require 'seatsio/util'

module Seatsio

  module_function

  class Chart

    attr_reader :id, :key, :status, :name, :published_version_thumbnail_url,
                :draft_version_thumbnail_url, :events, :tags, :archived,
                :categories, :validation, :social_distancing_rulesets

    def initialize(data)
      @id = data['id']
      @key = data['key']
      @status = data['status']
      @name = data['name']
      @published_version_thumbnail_url = data['publishedVersionThumbnailUrl']
      @draft_version_thumbnail_url = data['draftVersionThumbnailUrl']
      @events = Event.create_list(data['events']) if data['events']
      @tags = data['tags']
      @archived = data['archived']
      @validation = data['validation']
      @social_distancing_rulesets = data['socialDistancingRulesets'].map {
          |key, r| [key, SocialDistancingRuleset.new(r['name'],
                                                     number_of_disabled_seats_to_the_sides: r['numberOfDisabledSeatsToTheSides'],
                                                     disable_seats_in_front_and_behind: r['disableSeatsInFrontAndBehind'],
                                                     disable_diagonal_seats_in_front_and_behind: r['disableDiagonalSeatsInFrontAndBehind'],
                                                     number_of_disabled_aisle_seats: r['numberOfDisabledAisleSeats'],
                                                     max_group_size: r['maxGroupSize'],
                                                     max_occupancy_absolute: r['maxOccupancyAbsolute'],
                                                     max_occupancy_percentage: r['maxOccupancyPercentage'],
                                                     one_group_per_table: r['oneGroupPerTable'],
                                                     fixed_group_layout: r['fixedGroupLayout'],
                                                     disabled_seats: r['disabledSeats'],
                                                     enabled_seats: r['enabledSeats'],
                                                     index: r['index'])]
      }.to_h
    end
  end

  class ChartValidationResult
    attr_reader :errors, :warnings

    def initialize(data)
      @errors = data['errors']
      @warnings = data['warnings']
    end
  end

  class ChartDraft < Chart
    attr_reader :version

    def initialize(data)
      super(data)
      @version = data['version']
    end
  end

  class ForSaleConfig

    attr_reader :for_sale, :objects, :categories

    def initialize(data)
      if data
        @for_sale = data['forSale']
        @objects = data['objects']
        @categories = data['categories']
      end
    end
  end

  class TableBookingConfig

    attr_reader :mode, :tables

    def initialize(mode, tables = nil)
      @mode = mode
      @tables = tables
    end

    def self.inherit
      TableBookingConfig.new('INHERIT')
    end

    def self.all_by_seat
      TableBookingConfig.new('ALL_BY_SEAT')
    end

    def self.all_by_table
      TableBookingConfig.new('ALL_BY_TABLE')
    end

    def self.custom(tables)
      TableBookingConfig.new('CUSTOM', tables)
    end

    def self.from_json(data)
      if data
        TableBookingConfig.new(data['mode'], data['tables'])
      end
    end
  end

  class Channel
    attr_reader :key, :name, :color, :index, :objects

    def initialize(key, name, color, index, objects)
      @key = key
      @name = name
      @color = color
      @index = index
      @objects = objects
    end

    def == (other)
      self.key == other.key &&
          self.name == other.name &&
          self.color == other.color &&
          self.index == other.index &&
          self.objects == other.objects
    end

  end

  class Event

    attr_accessor :id, :key, :chart_key, :supports_best_available,
                  :table_booking_config, :for_sale_config, :created_on, :updated_on, :channels,
                  :social_distancing_ruleset_key

    def initialize(data)
      @id = data['id']
      @key = data['key']
      @chart_key = data['chartKey']
      @supports_best_available = data['supportsBestAvailable']
      @table_booking_config = TableBookingConfig::from_json(data['tableBookingConfig'])
      @for_sale_config = ForSaleConfig.new(data['forSaleConfig']) if data['forSaleConfig']
      @created_on = parse_date(data['createdOn'])
      @updated_on = parse_date(data['updatedOn'])
      @channels = data['channels'].map {
          |d| Channel.new(d['key'], d['name'], d['color'], d['index'], d['objects'])
      } if data['channels']
      @social_distancing_ruleset_key = data['socialDistancingRulesetKey']
    end

    def self.create_list(list = [])
      result = []

      list.each do |item|
        result << Event.new(item)
      end

      return result
    end
  end

  class Events
    attr_accessor :events

    def initialize(data)
      @events = Event.create_list(data['events']) if data['events']
    end
  end

  class APIResponse

    attr_reader :next_page_starts_after, :previous_page_ends_before, :items

    def initialize(data)
      @next_page_starts_after = data.fetch('next_page_starts_after', nil).to_i
      @previous_page_ends_before = data.fetch('previous_page_ends_before', nil).to_i
      @items = data.fetch('items', [])
    end
  end

  class Subaccount
    attr_reader :id, :secret_key, :designer_key, :public_key, :name, :active

    def initialize(data)
      @id = data['id']
      @public_key = data['publicKey']
      @secret_key = data['secretKey']
      @designer_key = data['designerKey']
      @name = data['name']
      @active = data['active']
    end
  end

  class Workspace
    attr_reader :id, :name, :key, :secret_key, :is_test, :is_active, :is_default

    def initialize(data)
      @id = data['id']
      @name = data['name']
      @key = data['key']
      @secret_key = data['secretKey']
      @is_test = data['isTest']
      @is_active = data['isActive']
      @is_default = data['isDefault']
    end
  end

  class ObjectStatus
    FREE = 'free'
    BOOKED = 'booked'
    HELD = 'reservedByToken'

    attr_reader :status, :hold_token, :order_id, :ticket_type,
                :quantity, :extra_data, :for_sale

    def initialize(data)
      @status = data['status']
      @hold_token = data['holdToken']
      @order_id = data['orderId']
      @ticket_type = data['ticketType']
      @quantity = data['quantity']
      @extra_data = data['extraData']
      @for_sale = data['forSale']
    end
  end

  class ChangeObjectStatusResult

    attr_reader :objects

    def initialize(data)
      @objects = Seatsio.to_object_details(data['objects']);
    end
  end

  class ChangeObjectStatusInBatchResult

    attr_reader :results

    def initialize(data)
      @results = data['results'].map { |r| ChangeObjectStatusResult.new(r) }
    end
  end

  class HoldToken

    attr_reader :hold_token, :expires_at, :expires_in_seconds, :workspace_key

    def initialize(data)
      @hold_token = data['holdToken']
      @expires_at = Time.parse(data['expiresAt'])
      @expires_in_seconds = data['expiresInSeconds']
      @workspace_key = data['workspaceKey']
    end
  end

  class BestAvailableObjects

    attr_reader :next_to_each_other, :objects, :object_details

    def initialize(data)
      @next_to_each_other = data['nextToEachOther']
      @objects = data['objects']
      @object_details = Seatsio.to_object_details(data['objectDetails'])
    end
  end

  class ChartReportItem

    attr_reader :label, :labels, :ids, :category_key, :category_label, :section, :entrance, :capacity, :object_type,
                :left_neighbour, :right_neighbour, :book_as_a_whole, :distance_to_focal_point

    def initialize(data)
      @label = data['label']
      @labels = data['labels']
      @ids = data['ids']
      @category_label = data['categoryLabel']
      @category_key = data['categoryKey']
      @section = data['section']
      @entrance = data['entrance']
      @capacity = data['capacity']
      @object_type = data['objectType']
      @left_neighbour = data['leftNeighbour']
      @right_neighbour = data['rightNeighbour']
      @book_as_a_whole = data['bookAsAWhole']
      @distance_to_focal_point = data['distanceToFocalPoint']
    end
  end

  class ChartReport

    attr_reader :items

    def initialize(data)
      items = {}
      data.each do |key, values|
        items[key] = []
        values.each do |value|
          items[key] << ChartReportItem.new(value)
        end
      end
      @items = items
    end
  end

  class EventReport

    attr_reader :items

    def initialize(data)
      if data.is_a? Array
        items = []
        data.each do |item|
          items << EventReportItem.new(item)
        end
        @items = items
      elsif data.nil?
        @items = []
      else
        items = {}
        data.each do |key, values|
          items[key] = []
          values.each do |value|
            items[key] << EventReportItem.new(value)
          end
        end
        @items = items
      end
    end
  end

  class EventReportItem
    attr_reader :labels, :ids, :label, :order_id, :extra_data, :capacity, :status,
                :category_key, :entrance, :object_type, :hold_token, :category_label,
                :ticket_type, :num_booked, :num_free, :num_held, :for_sale, :section,
                :is_accessible, :is_companion_seat, :has_restricted_view, :displayed_object_type,
                :left_neighbour, :right_neighbour, :is_selectable, :is_disabled_by_social_distancing, :channel,
                :book_as_a_whole, :distance_to_focal_point

    def initialize(data)
      @status = data['status']
      @label = data['label']
      @labels = data['labels']
      @ids = data['ids']
      @category_label = data['categoryLabel']
      @category_key = data['categoryKey']
      @ticket_type = data['ticketType']
      @order_id = data['orderId']
      @for_sale = data['forSale']
      @hold_token = data['holdToken']
      @section = data['section']
      @entrance = data['entrance']
      @num_booked = data['numBooked']
      @num_free = data['numFree']
      @num_held = data['numHeld']
      @capacity = data['capacity']
      @object_type = data['objectType']
      @extra_data = data['extraData']
      @is_accessible = data['isAccessible']
      @is_companion_seat = data['isCompanionSeat']
      @has_restricted_view = data['hasRestrictedView']
      @displayed_object_type = data['displayedObjectType']
      @left_neighbour = data['leftNeighbour']
      @right_neighbour = data['rightNeighbour']
      @is_selectable = data['isSelectable']
      @is_disabled_by_social_distancing = data['isDisabledBySocialDistancing']
      @channel = data['channel']
      @book_as_a_whole = data['bookAsAWhole']
      @distance_to_focal_point = data['distanceToFocalPoint']
    end
  end

  class UsageSummaryForAllMoths
    attr_reader :items

    def initialize(data)
      items = []
      data.each do |item|
        items << UsageSummaryForMonth.new(item)
      end
      @items = items
    end
  end

  class UsageSummaryForMonth
    def initialize(data)
      @month = Month.from_json(data['month'])
      @num_used_objects = data['numUsedObjects']
      @num_first_bookings = data['numFirstBookings']
      @num_first_bookings_by_status = data['numFirstBookingsByStatus']
      @num_first_bookings_or_selections = data['numFirstBookingsOrSelections']
    end
  end

  class UsageDetails

    attr_reader :workspace, :usage_by_chart

    def initialize(data)
      @workspace = data['workspace']
      @usage_by_chart = data['usageByChart'].map { |usage| UsageByChart.new(usage) }
    end
  end

  class UsageByChart

    attr_reader :chart, :usage_by_event

    def initialize(data)
      @chart = data['chart'] ? UsageChart.new(data['chart']) : nil
      @usage_by_event = data['usageByEvent'].map { |usage| UsageByEvent.new(usage) }
    end
  end

  class UsageChart

    attr_reader :name, :key

    def initialize(data)
      @name = data['name']
      @key = data['key']
    end
  end

  class UsageByEvent

    attr_reader :event, :num_used_objects, :num_first_bookings, :num_first_bookings_or_selections,
                :num_ga_selections_without_booking, :num_non_ga_selections_without_booking, :num_object_selections

    def initialize(data)
      @event = UsageEvent.new(data['event'])
      @num_used_objects = data['numUsedObjects']
      @num_first_bookings = data['numFirstBookings']
      @num_first_bookings_or_selections = data['numFirstBookingsOrSelections']
      @num_ga_selections_without_booking = data['numGASelectionsWithoutBooking']
      @num_non_ga_selections_without_booking = data['numNonGASelectionsWithoutBooking']
      @num_object_selections = data['numObjectSelections']
    end
  end

  class UsageEvent

    attr_reader :id, :key

    def initialize(data)
      @id = data['id']
      @key = data['key']
    end
  end

  class UsageForObject

    attr_reader :object, :num_first_bookings, :first_booking_date, :num_first_bookings, :num_first_bookings_or_selections

    def initialize(data)
      @object = data['object']
      @num_first_bookings = data['numFirstBookings']
      @first_booking_date = data['firstBookingDate'] ? DateTime.iso8601(data['firstBookingDate']) : nil
      @num_first_selections = data['numFirstSelections']
      @num_first_bookings_or_selections = data['numFirstBookingsOrSelections']
    end
  end

  class Month

    attr_reader :year, :month

    def self.from_json(data)
      @year = data['year']
      @month = data['month']
      self
    end

    def initialize(year, month)
      @year = year
      @month = month
    end

    def serialize
      @year.to_s + "-" + @month.to_s.rjust(2, "0")
    end
  end

  class StatusChange
    attr_reader :extra_data, :object_label, :date, :id, :status, :event_id, :origin, :order_id, :quantity, :hold_token

    def initialize(data)
      @id = data['id']
      @status = data['status']
      @date = DateTime.iso8601(data['date'])
      @object_label = data['objectLabel']
      @event_id = data['eventId']
      @extra_data = data['extraData']
      @origin = StatusChangeOrigin.new(data['origin'])
      @order_id = data['orderId']
      @quantity = data['quantity']
      @hold_token = data['holdToken']
    end
  end

  class StatusChangeOrigin
    attr_reader :type, :ip

    def initialize(data)
      @type = data['type']
      @ip = data['ip']
    end
  end

  def to_object_details(data)
    object_details = {}
    data.each do |key, value|
      object_details[key] = EventReportItem.new(value)
    end
    object_details
  end

  class SocialDistancingRuleset
    attr_reader :name, :number_of_disabled_seats_to_the_sides, :disable_seats_in_front_and_behind,
                :disable_diagonal_seats_in_front_and_behind, :number_of_disabled_aisle_seats, :max_group_size, :max_occupancy_absolute,
                :max_occupancy_percentage, :one_group_per_table, :fixed_group_layout, :disabled_seats, :enabled_seats, :index

    def initialize(name, number_of_disabled_seats_to_the_sides: 0, disable_seats_in_front_and_behind: false, disable_diagonal_seats_in_front_and_behind: false,
                   number_of_disabled_aisle_seats: 0, max_group_size: 0, max_occupancy_absolute: 0, max_occupancy_percentage: 0, one_group_per_table: false,
                   fixed_group_layout: false, disabled_seats: [], enabled_seats: [], index: 0)
      @name = name
      @number_of_disabled_seats_to_the_sides = number_of_disabled_seats_to_the_sides
      @disable_seats_in_front_and_behind = disable_seats_in_front_and_behind
      @disable_diagonal_seats_in_front_and_behind = disable_diagonal_seats_in_front_and_behind
      @number_of_disabled_aisle_seats = number_of_disabled_aisle_seats
      @max_group_size = max_group_size
      @max_occupancy_absolute = max_occupancy_absolute
      @max_occupancy_percentage = max_occupancy_percentage
      @one_group_per_table = one_group_per_table
      @fixed_group_layout = fixed_group_layout
      @disabled_seats = disabled_seats
      @enabled_seats = enabled_seats
      @index = index
    end

    def self.fixed(name, disabled_seats: [], index: 0)
      return SocialDistancingRuleset.new(name, index: index, disabled_seats: disabled_seats, fixed_group_layout: true)
    end

    def self.rule_based(name, number_of_disabled_seats_to_the_sides: 0, disable_seats_in_front_and_behind: false, disable_diagonal_seats_in_front_and_behind: false,
                        number_of_disabled_aisle_seats: 0, max_group_size: 0, max_occupancy_absolute: 0, max_occupancy_percentage: 0, one_group_per_table: false,
                        disabled_seats: [], enabled_seats: [], index: 0)
      return SocialDistancingRuleset.new(name,
                                         number_of_disabled_seats_to_the_sides: number_of_disabled_seats_to_the_sides,
                                         disable_seats_in_front_and_behind: disable_seats_in_front_and_behind,
                                         disable_diagonal_seats_in_front_and_behind: disable_diagonal_seats_in_front_and_behind,
                                         number_of_disabled_aisle_seats: number_of_disabled_aisle_seats,
                                         max_group_size: max_group_size,
                                         max_occupancy_absolute: max_occupancy_absolute,
                                         max_occupancy_percentage: max_occupancy_percentage,
                                         one_group_per_table: one_group_per_table,
                                         fixed_group_layout: false,
                                         disabled_seats: disabled_seats,
                                         enabled_seats: enabled_seats,
                                         index: index)
    end

    def == (other)
      self.name == other.name &&
          self.number_of_disabled_seats_to_the_sides == other.number_of_disabled_seats_to_the_sides &&
          self.disable_seats_in_front_and_behind == other.disable_seats_in_front_and_behind &&
          self.disable_diagonal_seats_in_front_and_behind == other.disable_diagonal_seats_in_front_and_behind &&
          self.number_of_disabled_aisle_seats == other.number_of_disabled_aisle_seats &&
          self.max_group_size == other.max_group_size &&
          self.max_occupancy_absolute == other.max_occupancy_absolute &&
          self.max_occupancy_percentage == other.max_occupancy_percentage &&
          self.one_group_per_table == other.one_group_per_table &&
          self.fixed_group_layout == other.fixed_group_layout &&
          self.disabled_seats == other.disabled_seats &&
          self.enabled_seats == other.enabled_seats &&
          self.index == other.index
    end
  end

end