
use crate::prelude::*;

define_derive_deftly! {
  /// Implements `InspectableConfigAuto`
  InspectableConfigAuto for struct, expect items:

  impl InspectableConfigAuto for $ttype {
    fn inspect_key_auto(&self, field: &'_ str)
                        -> Option<&dyn InspectableConfigValue> {
      Some(match field {
        $(
          stringify!($fname) => &self.$fname,
        )
        _ => return None,
      })
    }
  }
}

define_derive_deftly! {
  /// Generates config resolver method, only for `InstanceConfig`
  ///
  /// Each field ends up having an SKL and a method.
  /// The method actually looks up the value in a particular link context.
  /// SKL is passed to the method, which usually uses it to decide which
  /// sections to look in.  But it is also used by general validation,
  /// unconditionally, to reject settings in the wrong section.
  ///
  /// # Atrributes `#[deftly(ATTR)]`
  ///
  /// ## Overriding attributes (these take precedence)
  ///
  ///  * `skl = EXOR`: Set the SKL to `EXPR`.
  ///  * `special`: use `ResolveContext::special_FIELDNAME`
  ///
  /// ## Other attributes
  ///
  ///  | attribute    | SKL      | `ResolveContext` | `I.C.Global` | notes |
  ///  |--------------|-------------|------------------|-|-|
  ///  | `global`     | `Global`    |             | global | |
  ///  | `per_client` | `PerClient` |             | | |
  ///  | `client`     | `PerClient` | `client`    | | |
  ///  | `limited`    | `Limited`   | `limited`   | | |
  ///  | `computed`:  | `None`      | `computed`  | | |
  ///  | `server`     |             | `server`    | | usu. add `per_client`/`global` |
  ///  | _default otherwise unspecified_ | _error_ | `ordinary` | not global |
  ///
  /// (Blank cells indicate that the attribute doesn't affect that output.)
  ///
  /// ## Resulting information for each field
  ///
  ///  * SKL of type `SectionKindList`, ends in up in `FIELDS`.
  ///    Either `skl`, or exactly one of the SKL-setting attributes,
  ///    must be provided.
  ///
  ///  * How to resolve it: which method on `ResolveContext` to call.
  ///    Defaults to `ResolveContext::ordinary`.
  ///
  ///  * Whether to include the field in `InstanceConfigGlobal`.
  ///    (`global`).
  ///
  /// Generated code
  ///
  /// ```rust,ignore
  /// impl<'c> ResolveContext<'c> {
  ///
  ///   // SKL here is used by SectionKindList::contains()
  ///   const FIELDS: &'static [(&'static str, SectionKindList)] = &[ ... ];
  ///
  ///   #[throws(AE)]
  ///   fn resolve_instance(&self) -> InstanceConfig {
  ///     InstanceConfig {
  ///       ...
  ///        // SKL here is usually passed to first_of, but the method
  ///        // can do something more special.
  ///        max_batch_down: self.limited("max_batch_down", SKL::PerClient)?,
  ///        ...
  ///      }
  ///   }
  /// }
  ///
  /// pub struct InstanceConfigGlobal { ... }
  /// impl InspectableConfigAuto for InstanceConfigGlobal { ... }
  /// impl From<&'_ [InstanceConfig]> for InstanceConfigGlobal { .. }
  /// ```

  InstanceConfig expect items:

  ${define FIELD_SKL {
      ${if fmeta(skl) {
        ${fmeta(skl) as expr}
      } else {
        ${select1
          fmeta( per_client ) { SKL::PerClient }
          fmeta( client     ) { SKL::PerClient }
          fmeta( global     ) { SKL::Global    }
          fmeta( limited    ) { SKL::Limited   }
          fmeta( computed   ) { SKL::None      }
        }
      }}
  }}

  impl InstanceConfig {
    const FIELDS : & 'static [(& 'static str, SectionKindList)] = &[ $(
      (
        stringify!($fname),
        $FIELD_SKL,
      ),
    ) ];

    #[throws(AE)]
    fn resolve_instance(rctx: &ResolveContext) -> InstanceConfig {
      InstanceConfig {
        $(
          $fname: rctx.
            ${if fmeta(special) {
              ${paste special_ $fname}
            } else {
              ${select1
                fmeta( server     ) { server   }
                fmeta( client     ) { client   }
                fmeta( limited    ) { limited  }
                fmeta( computed   ) { computed }
                else                { ordinary }
              }
            }}
          (
            stringify!($fname), 
            $FIELD_SKL,
          )?,
        )
      }
    }
  }

  #[derive(Debug)]
  #[derive(Deftly)]
  #[derive_deftly(InspectableConfigAuto)]
  pub struct InstanceConfigGlobal {
    $(
      ${when fmeta(global)}
      pub $fname: $ftype,
    )
  }

  impl From<&'_ [InstanceConfig]> for InstanceConfigGlobal {
    fn from(l: &[InstanceConfig]) -> InstanceConfigGlobal {
      InstanceConfigGlobal {
        $(
          ${when fmeta(global)}
          $fname: <$ftype as ResolveGlobal>
            ::resolve(l.iter().map(|e| &e.$fname)),
        )
      }
    }
  }
}
