-- WirePlumber
--
-- Copyright © 2021-2022 Collabora Ltd.
--    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- Based on default-routes.c from pipewire-media-session
-- Copyright © 2020 Wim Taymans
--
-- SPDX-License-Identifier: MIT
--
-- Update the device info cache with the latest information from EnumRoute(all
-- the device routes) and trigger a "select-routes" event to select new routes
-- for the given device configuration, if it has changed

cutils = require ("common-utils")
devinfo = require ("device-info-cache")
log = Log.open_topic ("s-device")

SimpleEventHook {
  name = "device/select-route",
  after = "device/select-profile",
  interests = {
    EventInterest {
      Constraint { "event.type", "=", "device-added" },
    },
    EventInterest {
      Constraint { "event.type", "=", "device-params-changed" },
      Constraint { "event.subject.param-id", "c", "EnumRoute" },
    },
  },
  execute = function (event)
    local source = event:get_source ()
    local device = event:get_subject ()

    local dev_info = devinfo:get_device_info (device)
    if not dev_info then
      return
    end

    local new_route_infos = {}
    local avail_routes_changed = false
    local profile = nil

    -- get current profile
    for p in device:iterate_params ("Profile") do
      profile = cutils.parseParam (p, "Profile")
    end

    -- look at all the routes and update/reset cached information
    for p in device:iterate_params ("EnumRoute") do
      -- parse pod
      local route = cutils.parseParam (p, "EnumRoute")
      if not route then
        goto skip_enum_route
      end

      -- find cached route information
      local route_info = devinfo.find_route_info (dev_info, route, true)

      -- update properties
      route_info.prev_available = route_info.available
      if route_info.available ~= route.available then
        log:info (device, "route " .. route.name .. " available changed " ..
                         route_info.available .. " -> " .. route.available)
        route_info.available = route.available
        if profile and cutils.arrayContains (route.profiles, profile.index) then
          avail_routes_changed = true
        end
      end

      -- store
      new_route_infos [route.index] = route_info

      ::skip_enum_route::
    end

    -- replace old route_infos to lose old routes
    -- that no longer exist on the device
    dev_info.route_infos = new_route_infos
    new_route_infos = nil

    -- restore routes for profile
    if profile then
      local profile_changed = (dev_info.active_profile ~= profile.index)
      dev_info.active_profile = profile.index

      -- if the profile changed, restore routes for that profile
      -- if any of the routes of the current profile changed in availability,
      -- then try to select a new "best" route for each device and ignore
      -- what was stored
      if profile_changed or avail_routes_changed then
        log:info (device,
            string.format ("restore routes for profile(%s) of device(%s)",
            profile.name, dev_info.name))

        -- find the active device IDs for which to select routes
        local active_ids = findActiveDeviceIDs (profile)
        active_ids = Json.Array (active_ids):to_string ()

        -- push select-routes event and let the hooks select the appropriate routes
        local props = {
          ["profile.changed"] = profile_changed,
          ["profile.name"] = profile.name,
          ["profile.active-device-ids"] = active_ids,
        }
        source:call ("push-event", "select-routes", device, props)
      end
    end
  end
}:register()

-- These device ids are like routes(speaker, mic, headset etc) or sub-devices or
-- paths with in the pipewire devices/soundcards.
function findActiveDeviceIDs (profile)
  -- parses the classes from the profile and returns the device IDs
  ----- sample structure, should return { 0, 8 } -----
  -- classes:
  --  1: 2
  --  2:
  --    1: Audio/Source
  --    2: 1
  --    3: card.profile.devices
  --    4:
  --      1: 0
  --      pod_type: Array
  --      value_type: Spa:Int
  --    pod_type: Struct
  --  3:
  --    1: Audio/Sink
  --    2: 1
  --    3: card.profile.devices
  --    4:
  --      1: 8
  --      pod_type: Array
  --      value_type: Spa:Int
  --    pod_type: Struct
  --  pod_type: Struct
  local active_ids = {}
  if type (profile.classes) == "table" and profile.classes.pod_type == "Struct" then
    for _, p in ipairs (profile.classes) do
      if type (p) == "table" and p.pod_type == "Struct" then
        local i = 1
        while true do
          local k, v = p [i], p [i+1]
          i = i + 2
          if not k or not v then
            break
          end
          if k == "card.profile.devices" and
              type (v) == "table" and v.pod_type == "Array" then
            for _, dev_id in ipairs (v) do
              table.insert (active_ids, dev_id)
            end
          end
        end
      end
    end
  end
  return active_ids
end
