///|
/// The member name of a shape: the member part of its shape ID, else its name.
fn default_member_name(shape : @smithy.Shape) -> String {
  match shape.id.member_name() {
    Some(part) => part
    None => shape.id.name()
  }
}

///|
/// Maps a shape to its symbol and to its member name in the target language.
pub struct SymbolProvider {
  symbol_fn : (@smithy.Shape) -> Symbol raise CodegenError
  member_fn : (@smithy.Shape) -> String
}

///|
pub fn SymbolProvider::new(
  to_symbol : (@smithy.Shape) -> Symbol raise CodegenError,
  to_member_name~ : (@smithy.Shape) -> String = default_member_name,
) -> SymbolProvider {
  { symbol_fn: to_symbol, member_fn: to_member_name, }
}

///|
pub fn SymbolProvider::to_symbol(
  self : SymbolProvider,
  shape : @smithy.Shape,
) -> Symbol raise CodegenError {
  (self.symbol_fn)(shape)
}

///|
pub fn SymbolProvider::to_member_name(
  self : SymbolProvider,
  shape : @smithy.Shape,
) -> String {
  (self.member_fn)(shape)
}

///|
/// A provider that calls the base provider once for each shape ID.
/// It does not keep errors: a failed call runs again.
pub fn SymbolProvider::cached(self : SymbolProvider) -> SymbolProvider {
  let cache : Map[@smithy.ShapeId, Symbol] = Map([])
  let base = self.symbol_fn
  {
    symbol_fn: shape => {
      if cache.get(shape.id) is Some(symbol) {
        return symbol
      }
      let symbol = base(shape)
      cache[shape.id] = symbol
      symbol
    },
    member_fn: self.member_fn,
  }
}

///|
/// A provider that passes each symbol of the base provider through `f`.
pub fn SymbolProvider::decorate(
  self : SymbolProvider,
  f : (@smithy.Shape, Symbol) -> Symbol,
) -> SymbolProvider {
  let base = self.symbol_fn
  { symbol_fn: shape => f(shape, base(shape)), member_fn: self.member_fn, }
}

///|
/// A provider that escapes symbol names with `symbols` and member names with
/// `members`. The two sets of words are separate.
/// A symbol whose escaped name is not a valid `SymbolName` raises `InvalidSettings`.
pub fn SymbolProvider::with_reserved_words(
  self : SymbolProvider,
  symbols~ : ReservedWords,
  members~ : ReservedWords,
) -> SymbolProvider {
  let base = self.symbol_fn
  let base_member = self.member_fn
  {
    symbol_fn: shape => {
      let symbol = base(shape)
      let text = symbols.escape(symbol.name.to_string())
      match SymbolName::parse(text) {
        Some(name) => symbol.with_name(name)
        None =>
          raise InvalidSettings(
            "I could not use `\{text}` as the escaped name of shape `\{shape.id}`: a symbol name must not be empty or have whitespace.",
          )
      }
    },
    member_fn: shape => members.escape(base_member(shape)),
  }
}