<?php
namespace nur\ldap;

use nur\A;
use nur\data\types\md_utils;
use nur\data\types\Metadata;

class ldap {
  #############################################################################
  const ADD_SCHEMA = [
    "controls" => ["array", []],
  ];

  /** @var Metadata */
  private static $add_md;
  static function add_md(): Metadata {
    return md_utils::ensure_md(self::$add_md, self::ADD_SCHEMA);
  }

  static function add($conn, string $dn, array $attrs, $params=null): void {
    self::add_md()->ensureSchema($params);
    $r = LdapException::check("add", $conn
      , @ldap_add_ext($conn, $dn, $attrs, $params["controls"]));
    LdapException::check_result("add", $conn, $r);
  }

  #############################################################################
  const MODIFY_SCHEMA = [
    "controls" => ["array", []],
  ];

  /** @var Metadata */
  private static $modify_md;
  static function modify_md(): Metadata {
    return md_utils::ensure_md(self::$modify_md, self::MODIFY_SCHEMA);
  }

  static function prepare_modify(array $modattrs): array {
    $modifs = [];
    foreach ($modattrs as $modattr) {
      $modtype = false;
      $first = true;
      $index = 0;
      foreach ($modattr as $name => $value) {
        if ($first && $name === $index) {
          $first = false;
          $index++;
          switch ($value) {
          case "add":
            $modtype = LDAP_MODIFY_BATCH_ADD;
            break;
          case "delete":
            $modtype = LDAP_MODIFY_BATCH_REMOVE;
            break;
          case "replace":
            $modtype = LDAP_MODIFY_BATCH_REPLACE;
            break;
          }
          continue;
        }
        if ($name === $index) {
          $index++;
          $modifs[] = [
            "modtype" => LDAP_MODIFY_BATCH_REMOVE_ALL,
            "attrib" => $value,
          ];
        } else {
          $modifs[] = [
            "modtype" => $modtype,
            "attrib" => $name,
            "values" => $value
          ];
        }
      }
    }
    return $modifs;
  }

  static function modify($conn, string $dn, array $modattrs, $params=null): void {
    self::modify_md()->ensureSchema($params);
    $modifs = self::prepare_modify($modattrs);
    LdapException::check("modify", $conn
      , @ldap_modify_batch($conn, $dn, $modifs, $params["controls"]));
  }

  #############################################################################
  const RENAME_SCHEMA = [
    "new_parent" => ["?string", null],
    "delete_old_rdn" => ["bool", true],
    "controls" => ["array", []],
  ];

  /** @var Metadata */
  private static $rename_md;
  static function rename_md(): Metadata {
    return md_utils::ensure_md(self::$rename_md, self::RENAME_SCHEMA);
  }

  /**
   * préparer les paramètres pour le renommage
   *
   * si $newRdn n'est pas vide:
   * - si $params["new_parent"] n'est pas spécifié ou null, alors on ne fait
   * qu'un renommage: prendre le suffixe de $dn
   * - sinon, le nouveau DN est "$newRdn,$params[new_parent]"
   *
   * si $newRdn est vide:
   * - il s'agit d'un déplacement de branche. $params["new_parent"] ne doit pas
   * être vide et c'est la nouvelle destination. le RDN n'est pas modifié
   */
  static function prepare_rename(string $dn, string &$newRdn, &$params = null): bool {
    self::rename_md()->ensureSchema($params);
    names::split_dn($dn, $origRdn, $origParent);
    $newParent = $params["new_parent"];
    if ($newRdn != "") {
      # renommage et éventuellement déplacement
      if (strpos($newRdn, "=") === false) {
        # si le rdn ne comporte que la valeur, alors prendre le nom de
        # l'attribut depuis origRdn
        $name = A::first_key(names::split_rdn($origRdn));
        $newRdn = names::build_rdn($name, $newRdn);
      }
      if ($newParent === null) $newParent = $origParent;
    } else {
      # déplacement avec le même RDN
      $newRdn = $origRdn;
    }
    $newDn = names::join($newRdn, $newParent);
    names::split_dn($newDn, $newRdn, $newParent);
    $params["new_parent"] = $newParent;
    return $newDn !== $dn;
  }

  static function rename($conn, string $dn, string $newRdn, array $params): string {
    $newParent = $params["new_parent"];
    $r = LdapException::check("rename", $conn
      , @ldap_rename_ext($conn, $dn, $newRdn, $newParent
        , $params["delete_old_rdn"], $params["controls"]));
    LdapException::check_result("rename", $conn, $r);
    return names::join($newRdn, $newParent);
  }

  #############################################################################
  const DELETE_SCHEMA = [
    "controls" => ["array", []],
  ];

  /** @var Metadata */
  private static $delete_md;
  static function delete_md(): Metadata {
    return md_utils::ensure_md(self::$delete_md, self::DELETE_SCHEMA);
  }

  static function delete($conn, string $dn, $params=null): void {
    self::delete_md()->ensureSchema($params);
    $r = LdapException::check("delete", $conn
      , @ldap_delete_ext($conn, $dn, $params["controls"]));
    LdapException::check_result("delete", $conn, $r);
  }
}