"use strict";

window.COA = (function () {
  const tinctures = {
    field: {metals: 3, colours: 4, stains: +P(0.03), patterns: 1},
    division: {metals: 5, colours: 8, stains: +P(0.03), patterns: 1},
    charge: {metals: 2, colours: 3, stains: +P(0.05), patterns: 0},
    metals: {argent: 3, or: 2},
    colours: {gules: 5, azure: 4, sable: 3, purpure: 3, vert: 2},
    stains: {murrey: 1, sanguine: 1, tenné: 1},
    patterns: {
      semy: 8,
      ermine: 6,
      vair: 4,
      counterVair: 1,
      vairInPale: 1,
      vairEnPointe: 2,
      vairAncien: 2,
      potent: 2,
      counterPotent: 1,
      potentInPale: 1,
      potentEnPointe: 1,
      chequy: 8,
      lozengy: 5,
      fusily: 2,
      pally: 8,
      barry: 10,
      gemelles: 1,
      bendy: 8,
      bendySinister: 4,
      palyBendy: 2,
      barryBendy: 1,
      pappellony: 2,
      pappellony2: 3,
      scaly: 1,
      plumetty: 1,
      masoned: 6,
      fretty: 3,
      grillage: 1,
      chainy: 1,
      maily: 2,
      honeycombed: 1
    }
  };

  const charges = {
    types: {
      conventional: 30,
      crosses: 10,
      animals: 2,
      animalHeads: 1,
      birds: 2,
      aquatic: 1,
      seafaring: 1,
      fantastic: 3,
      plants: 1,
      agriculture: 1,
      arms: 3,
      bodyparts: 1,
      people: 1,
      architecture: 1,
      miscellaneous: 3,
      inescutcheon: 3,
      uploaded: 0
    },
    single: {
      conventional: 12,
      crosses: 8,
      plants: 2,
      animals: 10,
      animalHeads: 2,
      birds: 4,
      aquatic: 2,
      seafaring: 2,
      fantastic: 7,
      agriculture: 1,
      arms: 6,
      bodyparts: 1,
      people: 2,
      architecture: 1,
      miscellaneous: 10,
      inescutcheon: 5,
      uploaded: 0
    },
    semy: {conventional: 4, crosses: 1},
    conventional: {
      lozenge: 2,
      fusil: 4,
      mascle: 4,
      rustre: 2,
      lozengeFaceted: 3,
      lozengePloye: 1,
      roundel: 4,
      roundel2: 3,
      annulet: 4,
      mullet: 5,
      mulletPierced: 1,
      mulletFaceted: 1,
      mullet4: 3,
      mullet6: 4,
      mullet6Pierced: 1,
      mullet6Faceted: 1,
      mullet7: 1,
      mullet8: 1,
      mullet10: 1,
      estoile: 1,
      compassRose: 1,
      billet: 5,
      delf: 0,
      triangle: 3,
      trianglePierced: 1,
      goutte: 4,
      heart: 4,
      pique: 2,
      carreau: 1,
      trefle: 2,
      fleurDeLis: 6,
      sun: 3,
      sunInSplendour: 1,
      crescent: 5,
      fountain: 1
    },
    inescutcheon: {
      inescutcheonHeater: 1,
      inescutcheonSpanish: 1,
      inescutcheonFrench: 1,
      inescutcheonHorsehead: 1,
      inescutcheonHorsehead2: 1,
      inescutcheonPolish: 1,
      inescutcheonHessen: 1,
      inescutcheonSwiss: 1,
      inescutcheonBoeotian: 1,
      inescutcheonRoman: 1,
      inescutcheonKite: 1,
      inescutcheonOldFrench: 1,
      inescutcheonRenaissance: 1,
      inescutcheonBaroque: 1,
      inescutcheonTarge: 1,
      inescutcheonTarge2: 1,
      inescutcheonPavise: 1,
      inescutcheonWedged: 1,
      inescutcheonFlag: 1,
      inescutcheonPennon: 1,
      inescutcheonGuidon: 1,
      inescutcheonBanner: 1,
      inescutcheonDovetail: 1,
      inescutcheonGonfalon: 1,
      inescutcheonPennant: 1,
      inescutcheonRound: 1,
      inescutcheonOval: 1,
      inescutcheonVesicaPiscis: 1,
      inescutcheonSquare: 1,
      inescutcheonDiamond: 1,
      inescutcheonNo: 1,
      inescutcheonFantasy1: 1,
      inescutcheonFantasy2: 1,
      inescutcheonFantasy3: 1,
      inescutcheonFantasy4: 1,
      inescutcheonFantasy5: 1,
      inescutcheonNoldor: 1,
      inescutcheonGondor: 1,
      inescutcheonEasterling: 1,
      inescutcheonErebor: 1,
      inescutcheonIronHills: 1,
      inescutcheonUrukHai: 1,
      inescutcheonMoriaOrc: 1
    },
    crosses: {
      crossHummetty: 15,
      crossVoided: 1,
      crossPattee: 2,
      crossPatteeAlisee: 1,
      crossFormee: 1,
      crossFormee2: 2,
      crossPotent: 2,
      crossJerusalem: 1,
      crosslet: 1,
      crossClechy: 3,
      crossBottony: 1,
      crossFleury: 3,
      crossPatonce: 1,
      crossPommy: 1,
      crossGamma: 1,
      crossArrowed: 1,
      crossFitchy: 1,
      crossCercelee: 1,
      crossMoline: 2,
      crossFourchy: 1,
      crossAvellane: 1,
      crossErminee: 1,
      crossBiparted: 1,
      crossMaltese: 3,
      crossTemplar: 2,
      crossCeltic: 1,
      crossCeltic2: 1,
      crossTriquetra: 1,
      crossCarolingian: 1,
      crossOccitan: 1,
      crossSaltire: 3,
      crossBurgundy: 1,
      crossLatin: 3,
      crossPatriarchal: 1,
      crossOrthodox: 1,
      crossCalvary: 1,
      crossDouble: 1,
      crossTau: 1,
      crossSantiago: 1,
      crossAnkh: 1
    },
    animals: {
      lionRampant: 6,
      lionPassant: 2,
      lionPassantGuardant: 1,
      wolfRampant: 1,
      wolfPassant: 1,
      wolfStatant: 1,
      greyhoundCourant: 1,
      greyhoundSejant: 1,
      mastiffStatant: 1,
      boarRampant: 1,
      horseRampant: 2,
      horseSalient: 1,
      horsePassant: 1,
      bearRampant: 2,
      bearPassant: 1,
      bullPassant: 1,
      goat: 1,
      lamb: 1,
      lambPassantReguardant: 1,
      agnusDei: 1,
      elephant: 1,
      camel: 1,
      porcupine: 1,
      snake: 1
    },
    animalHeads: {wolfHeadErased: 2, bullHeadCaboshed: 1, deerHeadCaboshed: 1, lionHeadCaboshed: 2},
    fantastic: {
      dragonPassant: 2,
      dragonRampant: 2,
      wyvern: 1,
      wyvernWithWingsDisplayed: 1,
      griffinPassant: 1,
      griffinRampant: 1,
      eagleTwoHeards: 2,
      unicornRampant: 1,
      pegasus: 1,
      serpent: 1,
      basilisk: 1
    },
    birds: {eagle: 9, raven: 2, cock: 3, parrot: 1, swan: 2, swanErased: 1, heron: 1, owl: 1},
    plants: {tree: 1, oak: 1, cinquefoil: 1, rose: 1, apple: 1},
    aquatic: {escallop: 5, pike: 1, cancer: 1, dolphin: 1},
    seafaring: {anchor: 6, boat: 2, boat2: 1, lymphad: 2, armillarySphere: 1},
    agriculture: {garb: 2, rake: 1, plough: 2},
    arms: {
      sword: 4,
      falchion: 1,
      sabre: 1,
      sabresCrossed: 1,
      sabre2: 1,
      hatchet: 3,
      axe: 3,
      lochaberAxe: 1,
      mallet: 1,
      bowWithArrow: 3,
      bow: 1,
      arrow: 1,
      arrowsSheaf: 1,
      helmet: 2,
      cannon: 1
    },
    bodyparts: {hand: 4, head: 1, headWreathed: 1, foot: 1},
    people: {cavalier: 3, monk: 1, angel: 2},
    architecture: {tower: 1, castle: 1},
    miscellaneous: {
      crown: 2,
      crown2: 1,
      orb: 1,
      chalice: 1,
      key: 1,
      buckle: 1,
      bugleHorn: 1,
      bugleHorn2: 1,
      bell: 2,
      pot: 1,
      bucket: 1,
      horseshoe: 3,
      attire: 1,
      stagsAttires: 1,
      ramsHorn: 1,
      cowHorns: 2,
      wing: 1,
      wingSword: 1,
      lute: 1,
      harp: 1,
      wheel: 2,
      crosier: 1,
      fasces: 1,
      log: 1,
      chain: 1,
      anvil: 1
    },
    // selection based on culture type:
    Naval: {anchor: 3, boat: 1, lymphad: 2, armillarySphere: 1, escallop: 1, dolphin: 1},
    Highland: {tower: 1, raven: 1, wolfHeadErased: 1, wolfPassant: 1, goat: 1, axe: 1},
    River: {tower: 1, garb: 1, rake: 1, boat: 1, pike: 2, bullHeadCaboshed: 1, apple: 1, plough: 1},
    Lake: {cancer: 2, escallop: 1, pike: 2, heron: 1, boat: 1, boat2: 2},
    Nomadic: {pot: 1, buckle: 1, wheel: 2, sabre: 2, sabresCrossed: 1, bow: 2, arrow: 1, horseRampant: 1, horseSalient: 1, crescent: 1, camel: 3},
    Hunting: {
      bugleHorn: 2,
      bugleHorn2: 1,
      stagsAttires: 2,
      attire: 2,
      hatchet: 1,
      bowWithArrow: 1,
      arrowsSheaf: 1,
      deerHeadCaboshed: 1,
      wolfStatant: 1,
      oak: 1,
      greyhoundSejant: 1
    },
    // selection based on type
    City: {key: 3, bell: 2, lute: 1, tower: 1, castle: 1, mallet: 1, cannon: 1, anvil: 1},
    Capital: {crown: 2, orb: 1, lute: 1, castle: 3, tower: 1, crown2: 2},
    Сathedra: {chalice: 1, orb: 1, crosier: 2, lamb: 1, monk: 2, angel: 3, crossLatin: 2, crossPatriarchal: 1, crossOrthodox: 1, crossCalvary: 1, agnusDei: 3},
    // specific cases
    natural: {fountain: "azure", garb: "or", raven: "sable"}, // charges to mainly use predefined colours
    sinister: [
      // charges that can be sinister
      "crossGamma",
      "lionRampant",
      "lionPassant",
      "wolfRampant",
      "wolfPassant",
      "wolfStatant",
      "wolfHeadErased",
      "greyhoundСourant",
      "greyhoundSejant",
      "mastiffStatant",
      "boarRampant",
      "horseRampant",
      "horseSalient",
      "horsePassant",
      "bullPassant",
      "bearRampant",
      "bearPassant",
      "goat",
      "lamb",
      "lambPassantReguardant",
      "agnusDei",
      "elephant",
      "eagle",
      "raven",
      "cock",
      "parrot",
      "swan",
      "swanErased",
      "heron",
      "pike",
      "dragonPassant",
      "dragonRampant",
      "wyvern",
      "wyvernWithWingsDisplayed",
      "griffinPassant",
      "griffinRampant",
      "unicornRampant",
      "pegasus",
      "serpent",
      "hatchet",
      "lochaberAxe",
      "hand",
      "wing",
      "wingSword",
      "lute",
      "harp",
      "bow",
      "head",
      "headWreathed",
      "knight",
      "lymphad",
      "log",
      "crosier",
      "dolphin",
      "sabre",
      "monk",
      "owl",
      "axe",
      "camel",
      "fasces",
      "lionPassantGuardant",
      "helmet",
      "foot",
      "plough",
      "sabre2",
      "cannon",
      "porcupine",
      "basilisk",
      "snake",
      "anvil"
    ],
    reversed: [
      // charges that can be reversed
      "goutte",
      "mullet",
      "mullet7",
      "crescent",
      "crossTau",
      "cancer",
      "sword",
      "falchion",
      "sabresCrossed",
      "hand",
      "horseshoe",
      "bowWithArrow",
      "arrow",
      "arrowsSheaf",
      "rake",
      "crossTriquetra",
      "crossLatin",
      "crossTau",
      "sabre2"
    ]
  };

  const positions = {
    conventional: {
      e: 20,
      abcdefgzi: 3,
      beh: 3,
      behdf: 2,
      acegi: 1,
      kn: 3,
      bhdf: 1,
      jeo: 1,
      abc: 3,
      jln: 6,
      jlh: 3,
      kmo: 2,
      jleh: 1,
      def: 3,
      abcpqh: 4,
      ABCDEFGHIJKL: 1
    },
    complex: {e: 40, beh: 1, kn: 1, jeo: 1, abc: 2, jln: 7, jlh: 2, def: 1, abcpqh: 1},
    divisions: {
      perPale: {e: 15, pq: 5, jo: 2, jl: 2, ABCDEFGHIJKL: 1},
      perFess: {e: 12, kn: 4, jkl: 2, gizgiz: 1, jlh: 3, kmo: 1, ABCDEFGHIJKL: 1},
      perBend: {e: 5, lm: 5, bcfdgh: 1},
      perBendSinister: {e: 1, jo: 1},
      perCross: {e: 4, jlmo: 1, j: 1, jo: 2, jl: 1},
      perChevron: {e: 1, jlh: 1, dfk: 1, dfbh: 2, bdefh: 1},
      perChevronReversed: {e: 1, mok: 2, dfh: 2, dfbh: 1, bdefh: 1},
      perSaltire: {bhdf: 8, e: 3, abcdefgzi: 1, bh: 1, df: 1, ABCDEFGHIJKL: 1},
      perPile: {ee: 3, be: 2, abceh: 1, abcabc: 1, jleh: 1}
    },
    ordinariesOn: {
      pale: {ee: 12, beh: 10, kn: 3, bb: 1},
      fess: {ee: 1, def: 3},
      bar: {defdefdef: 1},
      fessCotissed: {ee: 1, def: 3},
      fessDoubleCotissed: {ee: 1, defdef: 3},
      bend: {ee: 2, jo: 1, joe: 1},
      bendSinister: {ee: 1, lm: 1, lem: 4},
      bendlet: {joejoejoe: 1},
      bendletSinister: {lemlemlem: 1},
      bordure: {ABCDEFGHIJKL: 1},
      chief: {abc: 5, bbb: 1},
      quarter: {jjj: 1},
      canton: {yyyy: 1},
      cross: {eeee: 1, behdfbehdf: 3, behbehbeh: 2},
      crossParted: {e: 5, ee: 1},
      saltire: {ee: 5, jlemo: 1},
      saltireParted: {e: 5, ee: 1},
      pall: {ee: 1, jleh: 5, jlhh: 3},
      pallReversed: {ee: 1, bemo: 5},
      pile: {bbb: 1},
      pileInBend: {eeee: 1, eeoo: 1},
      pileInBendSinister: {eeee: 1, eemm: 1}
    },
    ordinariesOff: {
      pale: {yyy: 1},
      fess: {abc: 3, abcz: 1},
      bar: {abc: 2, abcgzi: 1, jlh: 5, bgi: 2, ach: 1},
      gemelle: {abc: 1},
      bend: {ccg: 2, ccc: 1},
      bendSinister: {aai: 2, aaa: 1},
      bendlet: {ccg: 2, ccc: 1},
      bendletSinister: {aai: 2, aaa: 1},
      bordure: {e: 4, jleh: 2, kenken: 1, peqpeq: 1},
      orle: {e: 4, jleh: 1, kenken: 1, peqpeq: 1},
      chief: {emo: 2, emoz: 1, ez: 2},
      terrace: {e: 5, def: 1, bdf: 3},
      mount: {e: 5, def: 1, bdf: 3},
      point: {e: 2, def: 1, bdf: 3, acbdef: 1},
      flaunches: {e: 3, kn: 1, beh: 3},
      gyron: {bh: 1},
      quarter: {e: 1},
      canton: {e: 5, beh: 1, def: 1, bdefh: 1, kn: 1},
      cross: {acgi: 1},
      pall: {BCKFEILGJbdmfo: 1},
      pallReversed: {aczac: 1},
      chevron: {ach: 3, hhh: 1},
      chevronReversed: {bbb: 1},
      pile: {acdfgi: 1, acac: 1},
      pileInBend: {cg: 1},
      pileInBendSinister: {ai: 1},
      label: {defgzi: 2, eh: 3, defdefhmo: 1, egiegi: 1, pqn: 5}
    },
    // charges
    inescutcheon: {e: 4, jln: 1},
    mascle: {e: 15, abcdefgzi: 3, beh: 3, bdefh: 4, acegi: 1, kn: 3, joe: 2, abc: 3, jlh: 8, jleh: 1, df: 3, abcpqh: 4, pqe: 3, eknpq: 3},
    lionRampant: {e: 10, def: 2, abc: 2, bdefh: 1, kn: 1, jlh: 2, abcpqh: 1},
    lionPassant: {e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1},
    wolfPassant: {e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1},
    greyhoundСourant: {e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1},
    griffinRampant: {e: 10, def: 2, abc: 2, bdefh: 1, kn: 1, jlh: 2, abcpqh: 1},
    griffinPassant: {e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1},
    boarRampant: {e: 12, beh: 1, kn: 1, jln: 2},
    eagle: {e: 15, beh: 1, kn: 1, abc: 1, jlh: 2, def: 2, pq: 1},
    raven: {e: 15, beh: 1, kn: 1, jeo: 1, abc: 3, jln: 3, def: 1},
    wyvern: {e: 10, jln: 1},
    garb: {e: 1, def: 3, abc: 2, beh: 1, kn: 1, jln: 3, jleh: 1, abcpqh: 1, joe: 1, lme: 1},
    crown: {e: 10, abcdefgzi: 1, beh: 3, behdf: 2, acegi: 1, kn: 1, pq: 2, abc: 1, jln: 4, jleh: 1, def: 2, abcpqh: 3},
    hand: {e: 10, jln: 2, kn: 1, jeo: 1, abc: 2, pqe: 1},
    armillarySphere: {e: 1},
    tree: {e: 1},
    lymphad: {e: 1},
    head: {e: 1},
    headWreathed: {e: 1},
    cavalier: {e: 1},
    angel: {e: 1}
  };

  const lines = {
    straight: 50,
    wavy: 8,
    engrailed: 4,
    invecked: 3,
    rayonne: 3,
    embattled: 1,
    raguly: 1,
    urdy: 1,
    dancetty: 1,
    indented: 2,
    dentilly: 1,
    bevilled: 1,
    angled: 1,
    flechy: 1,
    barby: 1,
    enclavy: 1,
    escartely: 1,
    arched: 2,
    archedReversed: 1,
    nowy: 1,
    nowyReversed: 1,
    embattledGhibellin: 1,
    embattledNotched: 1,
    embattledGrady: 1,
    dovetailedIndented: 1,
    dovetailed: 1,
    potenty: 1,
    potentyDexter: 1,
    potentySinister: 1,
    nebuly: 2,
    seaWaves: 1,
    dragonTeeth: 1,
    firTrees: 1
  };

  const divisions = {
    variants: {
      perPale: 5,
      perFess: 5,
      perBend: 2,
      perBendSinister: 1,
      perChevron: 1,
      perChevronReversed: 1,
      perCross: 5,
      perPile: 1,
      perSaltire: 1,
      gyronny: 1,
      chevronny: 1
    },
    perPale: lines,
    perFess: lines,
    perBend: lines,
    perBendSinister: lines,
    perChevron: lines,
    perChevronReversed: lines,
    perCross: {
      straight: 20,
      wavy: 5,
      engrailed: 4,
      invecked: 3,
      rayonne: 1,
      embattled: 1,
      raguly: 1,
      urdy: 1,
      indented: 2,
      dentilly: 1,
      bevilled: 1,
      angled: 1,
      embattledGhibellin: 1,
      embattledGrady: 1,
      dovetailedIndented: 1,
      dovetailed: 1,
      potenty: 1,
      potentyDexter: 1,
      potentySinister: 1,
      nebuly: 1
    },
    perPile: lines
  };

  const ordinaries = {
    lined: {
      pale: 7,
      fess: 5,
      bend: 3,
      bendSinister: 2,
      chief: 5,
      bar: 2,
      gemelle: 1,
      fessCotissed: 1,
      fessDoubleCotissed: 1,
      bendlet: 2,
      bendletSinister: 1,
      terrace: 3,
      cross: 6,
      crossParted: 1,
      saltire: 2,
      saltireParted: 1
    },
    straight: {
      bordure: 8,
      orle: 4,
      mount: 1,
      point: 2,
      flaunches: 1,
      gore: 1,
      gyron: 1,
      quarter: 1,
      canton: 2,
      pall: 3,
      pallReversed: 2,
      chevron: 4,
      chevronReversed: 3,
      pile: 2,
      pileInBend: 2,
      pileInBendSinister: 1,
      piles: 1,
      pilesInPoint: 2,
      label: 1
    }
  };

  const shields = {
    types: {basic: 10, regional: 2, historical: 1, specific: 1, banner: 1, simple: 2, fantasy: 1, middleEarth: 0},
    basic: {heater: 12, spanish: 6, french: 1},
    regional: {horsehead: 1, horsehead2: 1, polish: 1, hessen: 1, swiss: 1},
    historical: {boeotian: 1, roman: 2, kite: 1, oldFrench: 5, renaissance: 2, baroque: 2},
    specific: {targe: 1, targe2: 0, pavise: 5, wedged: 10},
    banner: {flag: 1, pennon: 0, guidon: 0, banner: 0, dovetail: 1, gonfalon: 5, pennant: 0},
    simple: {round: 12, oval: 6, vesicaPiscis: 1, square: 1, diamond: 2, no: 0},
    fantasy: {fantasy1: 2, fantasy2: 2, fantasy3: 1, fantasy4: 1, fantasy5: 3},
    middleEarth: {noldor: 1, gondor: 1, easterling: 1, erebor: 1, ironHills: 1, urukHai: 1, moriaOrc: 1}
  };

  const generate = function (parent, kinship, dominion, type) {
    if (!parent || parent === "custom") {
      parent = null;
      kinship = 0;
      dominion = 0;
    }

    let usedPattern = null;
    let usedTinctures = [];

    const t1 = P(kinship) ? parent.t1 : getTincture("field");
    if (t1.includes("-")) usedPattern = t1;
    const coa = {t1};

    let charge = P(usedPattern ? 0.5 : 0.93) ? true : false; // 80% for charge
    const linedOrdinary = (charge && P(0.3)) || P(0.5) ? (parent?.ordinaries && P(kinship) ? parent.ordinaries[0].ordinary : rw(ordinaries.lined)) : null;
    const ordinary = (!charge && P(0.65)) || P(0.3) ? (linedOrdinary ? linedOrdinary : rw(ordinaries.straight)) : null; // 36% for ordinary
    const rareDivided = ["chief", "terrace", "chevron", "quarter", "flaunches"].includes(ordinary);
    const divisioned = rareDivided ? P(0.03) : charge && ordinary ? P(0.03) : charge ? P(0.3) : ordinary ? P(0.7) : P(0.995); // 33% for division
    const division = divisioned ? (parent?.division && P(kinship - 0.1) ? parent.division.division : rw(divisions.variants)) : null;
    if (charge)
      charge = parent?.charges && P(kinship - 0.1) ? parent.charges[0].charge : type && type !== "Generic" && P(0.2) ? rw(charges[type]) : selectCharge();

    if (division) {
      const t = getTincture("division", usedTinctures, P(0.98) ? coa.t1 : null);
      coa.division = {division, t};
      if (divisions[division]) coa.division.line = usedPattern || (ordinary && P(0.7)) ? "straight" : rw(divisions[division]);
    }

    if (ordinary) {
      coa.ordinaries = [{ordinary, t: getTincture("charge", usedTinctures, coa.t1)}];
      if (linedOrdinary) coa.ordinaries[0].line = usedPattern || (division && P(0.7)) ? "straight" : rw(lines);
      if (division && !charge && !usedPattern && P(0.5) && ordinary !== "bordure" && ordinary !== "orle") {
        if (P(0.8)) coa.ordinaries[0].divided = "counter";
        // 40%
        else if (P(0.6)) coa.ordinaries[0].divided = "field";
        // 6%
        else coa.ordinaries[0].divided = "division"; // 4%
      }
    }

    if (charge) {
      let p = "e",
        t = "gules";
      const ordinaryT = coa.ordinaries ? coa.ordinaries[0].t : null;
      if (positions.ordinariesOn[ordinary] && P(0.8)) {
        // place charge over ordinary (use tincture of field type)
        p = rw(positions.ordinariesOn[ordinary]);
        while (charges.natural[charge] === ordinaryT) charge = selectCharge();
        t = !usedPattern && P(0.3) ? coa.t1 : getTincture("charge", [], ordinaryT);
      } else if (positions.ordinariesOff[ordinary] && P(0.95)) {
        // place charge out of ordinary (use tincture of ordinary type)
        p = rw(positions.ordinariesOff[ordinary]);
        while (charges.natural[charge] === coa.t1) charge = selectCharge();
        t = !usedPattern && P(0.3) ? ordinaryT : getTincture("charge", usedTinctures, coa.t1);
      } else if (positions.divisions[division]) {
        // place charge in fields made by division
        p = rw(positions.divisions[division]);
        while (charges.natural[charge] === coa.t1) charge = selectCharge();
        t = getTincture("charge", ordinaryT ? usedTinctures.concat(ordinaryT) : usedTinctures, coa.t1);
      } else if (positions[charge]) {
        // place charge-suitable position
        p = rw(positions[charge]);
        while (charges.natural[charge] === coa.t1) charge = selectCharge();
        t = getTincture("charge", usedTinctures, coa.t1);
      } else {
        // place in standard position (use new tincture)
        p = usedPattern ? "e" : charges.conventional[charge] ? rw(positions.conventional) : rw(positions.complex);
        while (charges.natural[charge] === coa.t1) charge = selectCharge();
        t = getTincture("charge", usedTinctures.concat(ordinaryT), coa.t1);
      }

      if (charges.natural[charge]) t = charges.natural[charge]; // natural tincture
      coa.charges = [{charge, t, p}];

      if (p === "ABCDEFGHIKL" && P(0.95)) {
        // add central charge if charge is in bordure
        coa.charges[0].charge = rw(charges.conventional);
        const charge = selectCharge(charges.single);
        const t = getTincture("charge", usedTinctures, coa.t1);
        coa.charges.push({charge, t, p: "e"});
      } else if (P(0.8) && charge === "inescutcheon") {
        // add charge to inescutcheon
        const charge = selectCharge(charges.types);
        const t2 = getTincture("charge", [], t);
        coa.charges.push({charge, t: t2, p, size: 0.5});
      } else if (division && !ordinary) {
        const allowCounter = !usedPattern && (!coa.line || coa.line === "straight");

        // dimidiation: second charge at division basic positons
        if (P(0.3) && ["perPale", "perFess"].includes(division) && coa.line === "straight") {
          coa.charges[0].divided = "field";
          if (P(0.95)) {
            const p2 = p === "e" || P(0.5) ? "e" : rw(positions.divisions[division]);
            const charge = selectCharge(charges.single);
            const t = getTincture("charge", usedTinctures, coa.division.t);
            coa.charges.push({charge, t, p: p2, divided: "division"});
          }
        } else if (allowCounter && P(0.4)) coa.charges[0].divided = "counter";
        // counterchanged, 40%
        else if (["perPale", "perFess", "perBend", "perBendSinister"].includes(division) && P(0.8)) {
          // place 2 charges in division standard positions
          const [p1, p2] = division === "perPale" ? ["p", "q"] : division === "perFess" ? ["k", "n"] : division === "perBend" ? ["l", "m"] : ["j", "o"]; // perBendSinister
          coa.charges[0].p = p1;

          const charge = selectCharge(charges.single);
          const t = getTincture("charge", usedTinctures, coa.division.t);
          coa.charges.push({charge, t, p: p2});
        } else if (["perCross", "perSaltire"].includes(division) && P(0.5)) {
          // place 4 charges in division standard positions
          const [p1, p2, p3, p4] = division === "perCross" ? ["j", "l", "m", "o"] : ["b", "d", "f", "h"];
          coa.charges[0].p = p1;

          const c2 = selectCharge(charges.single);
          const t2 = getTincture("charge", [], coa.division.t);

          const c3 = selectCharge(charges.single);
          const t3 = getTincture("charge", [], coa.division.t);

          const c4 = selectCharge(charges.single);
          const t4 = getTincture("charge", [], coa.t1);
          coa.charges.push({charge: c2, t: t2, p: p2}, {charge: c3, t: t3, p: p3}, {charge: c4, t: t4, p: p4});
        } else if (allowCounter && p.length > 1) coa.charges[0].divided = "counter"; // counterchanged, 40%
      }

      coa.charges.forEach(c => defineChargeAttributes(c));
      function defineChargeAttributes(c) {
        // define size
        c.size = (c.size || 1) * getSize(c.p, ordinary, division);

        // clean-up position
        c.p = [...new Set(c.p)].join("");

        // define orientation
        if (P(0.02) && charges.sinister.includes(c.charge)) c.sinister = 1;
        if (P(0.02) && charges.reversed.includes(c.charge)) c.reversed = 1;
      }
    }

    // dominions have canton with parent coa
    if (P(dominion) && parent.charges) {
      const invert = isSameType(parent.t1, coa.t1);
      const t = invert ? getTincture("division", usedTinctures, coa.t1) : parent.t1;
      const canton = {ordinary: "canton", t};
      if (coa.charges) {
        coa.charges.forEach((charge, i) => {
          if (charge.size === 1.5) charge.size = 1.4;
          if (charge.p.includes("a")) charge.p = charge.p.replaceAll("a", "");
          if (charge.p.includes("j")) charge.p = charge.p.replaceAll("j", "");
          if (charge.p.includes("y")) charge.p = charge.p.replaceAll("y", "");
          if (!charge.p) coa.charges.splice(i, 1);
        });
      }

      let charge = parent.charges[0].charge;
      if (charge === "inescutcheon" && parent.charges[1]) charge = parent.charges[1].charge;

      let t2 = invert ? parent.t1 : parent.charges[0].t;
      if (isSameType(t, t2)) t2 = getTincture("charge", usedTinctures, t);

      if (!coa.charges) coa.charges = [];
      coa.charges.push({charge, t: t2, p: "y", size: 0.5});

      coa.ordinaries ? coa.ordinaries.push(canton) : (coa.ordinaries = [canton]);
    }

    function selectCharge(set) {
      const type = set ? rw(set) : ordinary || divisioned ? rw(charges.types) : rw(charges.single);
      return type === "inescutcheon" ? "inescutcheon" : rw(charges[type]);
    }

    // select tincture: element type (field, division, charge), used field tinctures, field type to follow RoT
    function getTincture(element, fields = [], RoT) {
      const base = RoT ? (RoT.includes("-") ? RoT.split("-")[1] : RoT) : null;

      let type = rw(tinctures[element]); // metals, colours, stains, patterns
      if (RoT && type !== "patterns") type = getType(base) === "metals" ? "colours" : "metals"; // follow RoT
      if (type === "metals" && fields.includes("or") && fields.includes("argent")) type = "colours"; // exclude metals overuse
      let tincture = rw(tinctures[type]);

      while (tincture === base || fields.includes(tincture)) {
        tincture = rw(tinctures[type]);
      } // follow RoT

      if (type !== "patterns" && element !== "charge") usedTinctures.push(tincture); // add field tincture

      if (type === "patterns") {
        usedPattern = tincture;
        tincture = definePattern(tincture, element);
      }

      return tincture;
    }

    function getType(t) {
      const tincture = t.includes("-") ? t.split("-")[1] : t;
      if (Object.keys(tinctures.metals).includes(tincture)) return "metals";
      if (Object.keys(tinctures.colours).includes(tincture)) return "colours";
      if (Object.keys(tinctures.stains).includes(tincture)) return "stains";
    }

    function isSameType(t1, t2) {
      return type(t1) === type(t2);

      function type(tincture) {
        if (Object.keys(tinctures.metals).includes(tincture)) return "metals";
        if (Object.keys(tinctures.colours).includes(tincture)) return "colours";
        if (Object.keys(tinctures.stains).includes(tincture)) return "stains";
        else return "pattern";
      }
    }

    function definePattern(pattern, element, size = "") {
      let t1 = null,
        t2 = null;
      if (P(0.1)) size = "-small";
      else if (P(0.1)) size = "-smaller";
      else if (P(0.01)) size = "-big";
      else if (P(0.005)) size = "-smallest";

      // apply standard tinctures
      if (P(0.5) && ["vair", "vairInPale", "vairEnPointe"].includes(pattern)) {
        t1 = "azure";
        t2 = "argent";
      } else if (P(0.8) && pattern === "ermine") {
        t1 = "argent";
        t2 = "sable";
      } else if (pattern === "pappellony") {
        if (P(0.2)) {
          t1 = "gules";
          t2 = "or";
        } else if (P(0.2)) {
          t1 = "argent";
          t2 = "sable";
        } else if (P(0.2)) {
          t1 = "azure";
          t2 = "argent";
        }
      } else if (pattern === "masoned") {
        if (P(0.3)) {
          t1 = "gules";
          t2 = "argent";
        } else if (P(0.3)) {
          t1 = "argent";
          t2 = "sable";
        } else if (P(0.1)) {
          t1 = "or";
          t2 = "sable";
        }
      } else if (pattern === "fretty") {
        if (t2 === "sable" || P(0.35)) {
          t1 = "argent";
          t2 = "gules";
        } else if (P(0.25)) {
          t1 = "sable";
          t2 = "or";
        } else if (P(0.15)) {
          t1 = "gules";
          t2 = "argent";
        }
      } else if (pattern === "semy") pattern += "_of_" + selectCharge(charges.semy);

      if (!t1 || !t2) {
        const startWithMetal = P(0.7);
        t1 = startWithMetal ? rw(tinctures.metals) : rw(tinctures.colours);
        t2 = startWithMetal ? rw(tinctures.colours) : rw(tinctures.metals);
      }

      // division should not be the same tincture as base field
      if (element === "division") {
        if (usedTinctures.includes(t1)) t1 = replaceTincture(t1);
        if (usedTinctures.includes(t2)) t2 = replaceTincture(t2);
      }

      usedTinctures.push(t1, t2);
      return `${pattern}-${t1}-${t2}${size}`;
    }

    function replaceTincture(t, n) {
      const type = getType(t);
      while (!n || n === t) {
        n = rw(tinctures[type]);
      }
      return n;
    }

    function getSize(p, o = null, d = null) {
      if (p === "e" && (o === "bordure" || o === "orle")) return 1.1;
      if (p === "e") return 1.5;
      if (p === "jln" || p === "jlh") return 0.7;
      if (p === "abcpqh" || p === "ez" || p === "be") return 0.5;
      if (["a", "b", "c", "d", "f", "g", "h", "i", "bh", "df"].includes(p)) return 0.5;
      if (["j", "l", "m", "o", "jlmo"].includes(p) && d === "perCross") return 0.6;
      if (p.length > 10) return 0.18; // >10 (bordure)
      if (p.length > 7) return 0.3; // 8, 9, 10
      if (p.length > 4) return 0.4; // 5, 6, 7
      if (p.length > 2) return 0.5; // 3, 4
      return 0.7; // 1, 2
    }

    return coa;
  };

  const getShield = function (culture, state) {
    const emblemShape = document.getElementById("emblemShape");
    const shapeGroup = emblemShape.selectedOptions[0]?.parentNode.label || "Diversiform";
    if (shapeGroup !== "Diversiform") return emblemShape.value;

    if (emblemShape.value === "state" && state && pack.states[state].coa) return pack.states[state].coa.shield;
    if (pack.cultures[culture].shield) return pack.cultures[culture].shield;
    ERROR && console.error("Shield shape is not defined on culture level", pack.cultures[culture]);
    return "heater";
  };

  const toString = coa => JSON.stringify(coa).replaceAll("#", "%23");
  const copy = coa => JSON.parse(JSON.stringify(coa));

  return {generate, toString, copy, getShield, shields};
})();
