/** @file ddc_display_selection.c */

// Copyright (C) 2022-2026 Sanford Rockowitz <rockowitz@minsoft.com>
// SPDX-License-Identifier: GPL-2.0-or-later

#include "config.h"

/** \cond */
#include <assert.h>
#include <errno.h>
#include <glib-2.0/glib.h>
#include <stdbool.h>
#include <string.h>

#include "util/debug_util.h"
#include "util/edid.h"
#include "util/report_util.h"
#include "util/string_util.h"
#ifdef ENABLE_UDEV
#include "util/udev_usb_util.h"
#include "util/udev_util.h"
#endif
/** \endcond */

#include "public/ddcutil_types.h"

#include "base/core.h"
#include "base/rtti.h"

#ifdef ENABLE_USB
#include "usb/usb_displays.h"
#endif

#include "ddc/ddc_display_ref_reports.h"
#include "ddc/ddc_displays.h"

#include "ddc/ddc_display_selection.h"

static DDCA_Trace_Group TRACE_GROUP = DDCA_TRC_DDC;


//
// Display Selection
//

/** Checks if a given #Display_Ref satisfies all the criteria specified in a
 *  #Display_Selector struct.
 *
 *  @param  dref     pointer to #Display_Ref to test
 *  @param  dsel     pointer to criteria
 *  @retval true     all specified criteria match
 *  @retval false    at least one specified criterion does not match
 *
 *  @remark
 *  In the degenerate case that no criteria are set in **dsel**, returns true.
 */
static bool
ddc_test_display_ref_by_selector(Display_Ref * dref, Display_Selector * dsel) {
   TRACED_ASSERT(dref && dsel);
   bool debug = false;
   DBGTRC_STARTING(debug, TRACE_GROUP, "dref=%s, dsel=%s", dref_repr_t(dref), dsel_repr_t(dsel));

   bool result = false;

   if (dsel->dispno >= 0 && dsel->dispno != dref->dispno)
      goto bye;

   if (dsel->busno >= 0) {
      if (dref->io_path.io_mode != DDCA_IO_I2C || dref->io_path.path.i2c_busno != dsel->busno)
         goto bye;
   }

#ifdef ENABLE_USB
   if (dsel->hiddev_devno >= 0) {
      if (dref->io_path.io_mode != DDCA_IO_USB)
         goto bye;
      char buf[40];
      snprintf(buf, 40, "%s/hiddev%d", usb_hiddev_directory(), dsel->hiddev_devno);
      Usb_Monitor_Info * moninfo = dref->detail;
      TRACED_ASSERT(memcmp(moninfo->marker, USB_MONITOR_INFO_MARKER, 4) == 0);
      if (!streq( moninfo->hiddev_device_name, buf))
         goto bye;
   }

   if (dsel->usb_bus >= 0) {
      if (dref->io_path.io_mode != DDCA_IO_USB)
         goto bye;
      // Usb_Monitor_Info * moninfo = drec->detail2;
      // assert(memcmp(moninfo->marker, USB_MONITOR_INFO_MARKER, 4) == 0);
      // if ( moninfo->hiddev_devinfo->busnum != criteria->usb_busno )
      if ( dref->usb_bus != dsel->usb_bus )
         goto bye;
   }

   if (dsel->usb_device >= 0) {
      if (dref->io_path.io_mode != DDCA_IO_USB)
         goto bye;
      // Usb_Monitor_Info * moninfo = drec->detail2;
      // assert(memcmp(moninfo->marker, USB_MONITOR_INFO_MARKER, 4) == 0);
      // if ( moninfo->hiddev_devinfo->devnum != criteria->usb_devno )
      if ( dref->usb_device != dsel->usb_device )
         goto bye;
   }

   if (dsel->hiddev_devno >= 0) {
      if (dref->io_path.io_mode != DDCA_IO_USB)
         goto bye;
      if ( dref->io_path.path.hiddev_devno != dsel->hiddev_devno )
         goto bye;
   }
#endif

   if (dsel->mfg_id && (strlen(dsel->mfg_id) > 0) &&
         !streqcase(dref->pedid->mfg_id, dsel->mfg_id) )
      goto bye;

   if (dsel->model_name && (strlen(dsel->model_name) > 0) &&
         !streqcase(dref->pedid->model_name, dsel->model_name) )
      goto bye;

   if (dsel->serial_ascii && (strlen(dsel->serial_ascii) > 0) &&
         !streqcase(dref->pedid->serial_ascii, dsel->serial_ascii) )
      goto bye;

   if (dsel->edidbytes) {
      assert(dsel->edidbytect > 0 && dsel->edidbytect <= 128);
      int startpos = 128 - dsel->edidbytect;
      if (memcmp(dref->pedid->bytes+startpos, dsel->edidbytes, dsel->edidbytect) != 0)
         goto bye;
   }
   result = true;

bye:
   DBGTRC_RET_BOOL(debug, TRACE_GROUP, result, "");
   return result;
}


/** Finds the first display reference that matches a #Display_Selector.
 *  Phantom displays are ignored.
 *
 *  @param  dsel  criteria to check
 *  @return display reference if found, NULL if not
 */
Display_Ref *
ddc_find_display_ref_by_selector(Display_Selector * dsel) {
   bool debug = false;
   DBGTRC_STARTING(debug, TRACE_GROUP, "dsel=%p", dsel);

   Display_Ref * result = NULL;

   GPtrArray * all_displays = ddc_get_all_display_refs();
   for (int ndx = 0; ndx < all_displays->len; ndx++) {
      Display_Ref * drec = g_ptr_array_index(all_displays, ndx);
      TRACED_ASSERT(memcmp(drec->marker, DISPLAY_REF_MARKER, 4) == 0);
      if (drec->dispno != DISPNO_PHANTOM) {   // Ignore phantom displays
         if (ddc_test_display_ref_by_selector(drec, dsel)) {
            result = drec;
            break;
         }
      }
   }

   DBGTRC_RET_STRING(debug, TRACE_GROUP, dref_repr_t(result), "result=%p", result);
   return result;
}


void
init_ddc_display_selection() {
   RTTI_ADD_FUNC(ddc_find_display_ref_by_selector);
   RTTI_ADD_FUNC(ddc_test_display_ref_by_selector);
}

