|  | import { LogEntry } from '@/game/interface'
export enum POV {First, Second, Third}
export type SoloLine<T> = (user: T) => LogEntry
export type SoloLineArgs<T, V> = (user: T, args: V) => LogEntry
export type PairLine<T> = (user: T, target: T) => LogEntry
export type PairLineArgs<T, V> = (user: T, target: T, args: V) => LogEntry
export type GroupLine<T> = (user: T, targets: Array<T>) => LogEntry
enum NounKind {
    Specific,
    Nonspecific,
    All
}
enum VowelSound {
    Default,
    Vowel,
    NonVowel
}
enum VerbKind {
  Root,
  Singular,
  Present,
  Past,
  PastParticiple
}
export interface Pluralizable {
  isPlural: boolean;
}
interface WordOptions {
  plural: boolean;
  capital: boolean;
  allCaps: boolean;
  proper: boolean;
  nounKind: NounKind;
  verbKind: VerbKind;
  vowel: VowelSound;
  count: boolean;
  possessive: boolean;
  objective: boolean;
  perspective: POV;
}
const emptyConfig: WordOptions = {
  allCaps: false,
  capital: false,
  count: false,
  nounKind: NounKind.Specific,
  verbKind: VerbKind.Root,
  plural: false,
  proper: false,
  vowel: VowelSound.Default,
  possessive: false,
  objective: false,
  perspective: POV.Third
}
export type TextLike = { toString: () => string }
// updates as needed
export class LiveText<T> {
  constructor (private contents: T, private run: (thing: T) => TextLike) {
  }
  toString (): string {
    return this.run(this.contents).toString()
  }
}
export class DynText {
  private parts: Array<TextLike>
  constructor (...parts: TextLike[]) {
    this.parts = parts
  }
  toString (): string {
    return (this.parts.map(part => part.toString())).join('')
  }
}
export abstract class Word {
  constructor (public opt: WordOptions = emptyConfig) {
  }
  abstract configure (opts: WordOptions): Word;
  abstract toString (): string;
  // These functions are pure; they don't mutate the original object.
  // This is necessary to avoid causing chaos.
  get allCaps (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.allCaps = true
    return this.configure(opts) as this
  }
  get capital (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.capital = true
    return this.configure(opts) as this
  }
  get plural (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.plural = true
    return this.configure(opts) as this
  }
  get proper (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.proper = true
    return this.configure(opts) as this
  }
  get improper (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.proper = false
    return this.configure(opts) as this
  }
  get specific (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.nounKind = NounKind.Specific
    return this.configure(opts) as this
  }
  get nonspecific (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.nounKind = NounKind.Nonspecific
    return this.configure(opts) as this
  }
  get all (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.nounKind = NounKind.All
    return this.configure(opts) as this
  }
  get uncountable (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.count = false
    return this.configure(opts) as this
  }
  get root (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.verbKind = VerbKind.Root
    return this.configure(opts) as this
  }
  get singular (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.verbKind = VerbKind.Singular
    return this.configure(opts) as this
  }
  get present (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.verbKind = VerbKind.Present
    return this.configure(opts) as this
  }
  get past (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.verbKind = VerbKind.Past
    return this.configure(opts) as this
  }
  get pastParticiple (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.verbKind = VerbKind.PastParticiple
    return this.configure(opts) as this
  }
  get possessive (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.possessive = true
    return this.configure(opts) as this
  }
  get objective (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.objective = true
    return this.configure(opts) as this
  }
  get pov1 (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.perspective = POV.First
    return this.configure(opts) as this
  }
  get pov2 (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.perspective = POV.Second
    return this.configure(opts) as this
  }
  get pov3 (): this {
    const opts: WordOptions = Object.assign({}, this.opt)
    opts.perspective = POV.Third
    return this.configure(opts) as this
  }
}
export class OptionalWord extends Word {
  constructor (public word: Word, private chance = 0.5, private suffix = " ") {
    super(word.opt)
  }
  configure (opts: WordOptions): Word {
    return new OptionalWord(this.word.configure(opts), this.chance, this.suffix)
  }
  toString (): string {
    if (Math.random() < this.chance) {
      return this.word + this.suffix
    } else {
      return ""
    }
  }
}
export class RandomWord extends Word {
  private history: { last: number }
  constructor (public choices: Array<Word>, opt: WordOptions = emptyConfig, history: { last: number } = { last: -1 }) {
    super(opt)
    this.history = history
  }
  configure (opts: WordOptions): Word {
    return new RandomWord(this.choices, opts, this.history)
  }
  toString (): string {
    let choice
    do {
      choice = Math.floor(Math.random() * this.choices.length)
    } while (choice === this.history.last && this.choices.length > 1)
    this.history.last = choice
    return this.choices[choice].configure(this.opt).toString()
  }
}
export class Noun extends Word {
  constructor (protected singularNoun: string, protected pluralNoun: string|null = null, protected possessiveNoun: string|null = null, protected options: WordOptions = emptyConfig) {
    super(options)
  }
  configure (opts: WordOptions): Word {
    return new Noun(this.singularNoun, this.pluralNoun, this.possessiveNoun, opts)
  }
  toString (): string {
    let result: string
    // TODO: plural possessive nouns?
    if (this.options.possessive) {
      if (this.possessiveNoun === null) {
        result = this.singularNoun + "'s"
      } else {
        result = this.possessiveNoun
      }
    } else if (this.options.plural) {
      if (this.pluralNoun === null) {
        result = this.singularNoun
      } else {
        result = (this.pluralNoun as string)
      }
    } else {
      result = this.singularNoun
    }
    if (!this.options.proper) {
      if (this.options.nounKind === NounKind.Nonspecific && this.options.count) {
        if (this.options.plural) {
          result = 'some ' + result
        } else {
          if (this.options.vowel === VowelSound.Default) {
            if ('aeiouAEIOU'.indexOf(result.slice(0, 1)) >= 0) {
              result = 'an ' + result
            } else {
              result = 'a ' + result
            }
          } else if (this.options.vowel === VowelSound.Vowel) {
            result = 'an ' + result
          } else if (this.options.vowel === VowelSound.NonVowel) {
            result = 'a ' + result
          }
        }
      } else if (this.options.nounKind === NounKind.Specific) {
        result = 'the ' + result
      }
    }
    if (this.options.allCaps) {
      result = result.toUpperCase()
    } else if (this.options.capital) {
      result = result.slice(0, 1).toUpperCase() + result.slice(1)
    }
    return result
  }
  conjugate (verb: Word): Word {
    if (this.opt.plural) {
      return verb.root
    } else {
      return verb.singular
    }
  }
}
export class ImproperNoun extends Noun {
  constructor (singularNoun: string, pluralNoun: string = singularNoun) {
    super(singularNoun, pluralNoun, null, { plural: false, allCaps: false, capital: false, proper: false, nounKind: NounKind.Specific, verbKind: VerbKind.Root, vowel: VowelSound.Default, count: true, possessive: false, objective: false, perspective: POV.Third })
  }
}
export class ProperNoun extends Noun {
  constructor (singularNoun: string) {
    super(singularNoun, null, null, { plural: false, allCaps: false, capital: false, proper: true, nounKind: NounKind.Specific, verbKind: VerbKind.Root, vowel: VowelSound.Default, count: true, possessive: false, objective: false, perspective: POV.Third })
  }
}
export class Adjective extends Word {
  constructor (private adjective: string, opt: WordOptions = emptyConfig) {
    super(opt)
  }
  configure (opts: WordOptions): Word {
    return new Adjective(this.adjective, opts)
  }
  // TODO caps et al.
  toString (): string {
    return this.adjective
  }
}
export class Adverb extends Word {
  constructor (private adverb: string, opt: WordOptions = emptyConfig) {
    super(opt)
  }
  configure (opt: WordOptions): Word {
    return new Adverb(this.adverb, opt)
  }
  toString (): string {
    return this.adverb
  }
}
export class Verb extends Word {
  constructor (private _root: string, private _singular: string = _root + "s", private _present: string = _root + "ing", private _past: string = _root + "ed", private _pastParticiple: string = _past, public opt: WordOptions = emptyConfig) {
    super(opt)
  }
  configure (opts: WordOptions): Word {
    return new Verb(
      this._root,
      this._singular,
      this._present,
      this._past,
      this._pastParticiple,
      opts
    )
  }
  toString (): string {
    let choice: string
    switch (this.opt.verbKind) {
      case VerbKind.Root: choice = this._root; break
      case VerbKind.Singular: choice = this._singular; break
      case VerbKind.Present: choice = this._present; break
      case VerbKind.Past: choice = this._past; break
      case VerbKind.PastParticiple: choice = this._pastParticiple; break
    }
    if (this.opt.allCaps) {
      choice = choice.toUpperCase()
    } else if (this.opt.capital) {
      choice = choice.slice(0, 1).toUpperCase() + choice.slice(1)
    }
    return choice
  }
}
export class Preposition extends Word {
  constructor (private word: string, public opt: WordOptions = emptyConfig) {
    super(opt)
  }
  configure (opts: WordOptions): Word {
    return new Preposition(this.word, opts)
  }
  toString (): string {
    if (this.opt.capital) {
      return this.word.slice(0, 1).toUpperCase() + this.word.slice(1)
    } else {
      return this.word
    }
  }
}
// this one is obnoxious
export class ToBe extends Word {
  constructor (protected opts: WordOptions = emptyConfig) {
    super(opts)
  }
  configure (opts: WordOptions): Word {
    return new ToBe(opts)
  }
  toString (): string {
    let choice
    if (this.opts.plural) {
      choice = 'are'
    }
    switch (this.opts.perspective) {
      case POV.First: choice = 'am'; break
      case POV.Second: choice = 'are'; break
      case POV.Third: choice = 'is'; break
    }
    if (this.opt.allCaps) {
      choice = choice.toUpperCase()
    } else if (this.opt.capital) {
      choice = choice.slice(0, 1).toUpperCase() + choice.slice(1)
    }
    return choice
  }
}
interface PronounDict {
    subjective: string;
    objective: string;
    possessive: string;
    reflexive: string;
}
export class Pronoun implements Pluralizable {
  constructor (private pronouns: PronounDict, private capitalize: boolean = false, public isPlural: boolean = false) {
  }
  get capital (): Pronoun {
    return new Pronoun(this.pronouns, true)
  }
  get subjective (): string {
    return this.caps(this.pronouns.subjective)
  }
  get objective (): string {
    return this.caps(this.pronouns.objective)
  }
  get possessive (): string {
    return this.caps(this.pronouns.possessive)
  }
  get reflexive (): string {
    return this.caps(this.pronouns.reflexive)
  }
  conjugate (verb: Word): Word {
    if (this.isPlural) {
      return verb.root
    } else {
      return verb.singular
    }
  }
  private caps (input: string): string {
    if (this.capitalize) {
      return input.slice(0, 1).toUpperCase() + input.slice(1)
    } else {
      return input
    }
  }
}
export const MalePronouns = new Pronoun({
  subjective: 'he',
  objective: 'him',
  possessive: 'his',
  reflexive: 'himself'
})
export const FemalePronouns = new Pronoun({
  subjective: 'she',
  objective: 'her',
  possessive: 'her',
  reflexive: 'herself'
})
export const TheyPronouns = new Pronoun({
  subjective: 'they',
  objective: 'them',
  possessive: 'their',
  reflexive: 'themself'
}, false, true)
export const TheyPluralPronouns = new Pronoun({
  subjective: 'they',
  objective: 'them',
  possessive: 'their',
  reflexive: 'themselves'
}, false, true)
export const ObjectPronouns = new Pronoun({
  subjective: 'it',
  objective: 'it',
  possessive: 'its',
  reflexive: 'itself'
})
export const SecondPersonPronouns = new Pronoun({
  subjective: 'you',
  objective: 'you',
  possessive: 'your',
  reflexive: 'yourself'
})
export const FirstPersonPronouns = new Pronoun({
  subjective: 'I',
  objective: 'me',
  possessive: 'my',
  reflexive: 'myself'
})
export class PronounAsNoun extends Noun {
  constructor (private pronouns: Pronoun, opt: WordOptions = emptyConfig) {
    super(pronouns.subjective, pronouns.subjective, pronouns.possessive, opt)
    this.options.nounKind = NounKind.All
    this.options.plural = true
  }
  configure (opts: WordOptions): Word {
    return new PronounAsNoun(this.pronouns, opts)
  }
  toString (): string {
    if (this.options.objective) {
      return new Noun(this.pronouns.objective, this.pronouns.objective, this.pronouns.possessive, this.options).toString()
    } else {
      return super.toString()
    }
  }
  conjugate (verb: Word): Word {
    if (this.pronouns === FirstPersonPronouns) {
      return super.conjugate(verb.pov1)
    } else if (this.pronouns === SecondPersonPronouns) {
      return super.conjugate(verb.pov2)
    } else {
      return super.conjugate(verb.pov3)
    }
  }
}
 |