"use strict";

window.Religions = (function () {
  // name generation approach and relative chance to be selected
  const approach = {Number: 1, Being: 3, Adjective: 5, "Color + Animal": 5, "Adjective + Animal": 5, "Adjective + Being": 5, "Adjective + Genitive": 1, "Color + Being": 3, "Color + Genitive": 3, "Being + of + Genitive": 2, "Being + of the + Genitive": 1, "Animal + of + Genitive": 1, "Adjective + Being + of + Genitive": 2, "Adjective + Animal + of + Genitive": 2};

  // turn weighted array into simple array
  const approaches = [];
  for (const a in approach) {
    for (let j = 0; j < approach[a]; j++) {
      approaches.push(a);
    }
  }

  const base = {
    number: ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve"],
    being: ["God", "Goddess", "Lord", "Lady", "Deity", "Creator", "Maker", "Overlord", "Ruler", "Chief", "Master", "Spirit", "Ancestor", "Father", "Forebear", "Forefather", "Mother", "Brother", "Sister", "Elder", "Numen", "Ancient", "Virgin", "Giver", "Council", "Guardian", "Reaper"],
    animal: ["Dragon", "Wyvern", "Phoenix", "Unicorn", "Sphinx", "Centaur", "Pegasus", "Kraken", "Basilisk", "Chimera", "Cyclope", "Antelope", "Ape", "Badger", "Bear", "Beaver", "Bison", "Boar", "Buffalo", "Cat", "Cobra", "Crane", "Crocodile", "Crow", "Deer", "Dog", "Eagle", "Elk", "Fox", "Goat", "Goose", "Hare", "Hawk", "Heron", "Horse", "Hyena", "Ibis", "Jackal", "Jaguar", "Lark", "Leopard", "Lion", "Mantis", "Marten", "Moose", "Mule", "Narwhal", "Owl", "Panther", "Rat", "Raven", "Rook", "Scorpion", "Shark", "Sheep", "Snake", "Spider", "Swan", "Tiger", "Turtle", "Viper", "Vulture", "Walrus", "Wolf", "Wolverine", "Worm", "Camel", "Falcon", "Hound", "Ox", "Serpent"],
    adjective: ["New", "Good", "High", "Old", "Great", "Big", "Young", "Major", "Strong", "Happy", "Last", "Main", "Huge", "Far", "Beautiful", "Wild", "Fair", "Prime", "Crazy", "Ancient", "Proud", "Secret", "Lucky", "Sad", "Silent", "Latter", "Severe", "Fat", "Holy", "Pure", "Aggressive", "Honest", "Giant", "Mad", "Pregnant", "Distant", "Lost", "Broken", "Blind", "Friendly", "Unknown", "Sleeping", "Slumbering", "Loud", "Hungry", "Wise", "Worried", "Sacred", "Magical", "Superior", "Patient", "Dead", "Deadly", "Peaceful", "Grateful", "Frozen", "Evil", "Scary", "Burning", "Divine", "Bloody", "Dying", "Waking", "Brutal", "Unhappy", "Calm", "Cruel", "Favorable", "Blond", "Explicit", "Disturbing", "Devastating", "Brave", "Sunny", "Troubled", "Flying", "Sustainable", "Marine", "Fatal", "Inherent", "Selected", "Naval", "Cheerful", "Almighty", "Benevolent", "Eternal", "Immutable", "Infallible"],
    genitive: ["Day", "Life", "Death", "Night", "Home", "Fog", "Snow", "Winter", "Summer", "Cold", "Springs", "Gates", "Nature", "Thunder", "Lightning", "War", "Ice", "Frost", "Fire", "Doom", "Fate", "Pain", "Heaven", "Justice", "Light", "Love", "Time", "Victory"],
    theGenitive: ["World", "Word", "South", "West", "North", "East", "Sun", "Moon", "Peak", "Fall", "Dawn", "Eclipse", "Abyss", "Blood", "Tree", "Earth", "Harvest", "Rainbow", "Sea", "Sky", "Stars", "Storm", "Underworld", "Wild"],
    color: ["Dark", "Light", "Bright", "Golden", "White", "Black", "Red", "Pink", "Purple", "Blue", "Green", "Yellow", "Amber", "Orange", "Brown", "Grey"]
  };

  const forms = {
    Folk: {Shamanism: 2, Animism: 2, "Ancestor worship": 1, Polytheism: 2},
    Organized: {Polytheism: 5, Dualism: 1, Monotheism: 4, "Non-theism": 1},
    Cult: {Cult: 1, "Dark Cult": 1},
    Heresy: {Heresy: 1}
  };

  const methods = {"Random + type": 3, "Random + ism": 1, "Supreme + ism": 5, "Faith of + Supreme": 5, "Place + ism": 1, "Culture + ism": 2, "Place + ian + type": 6, "Culture + type": 4};

  const types = {
    Shamanism: {Beliefs: 3, Shamanism: 2, Spirits: 1},
    Animism: {Spirits: 1, Beliefs: 1},
    "Ancestor worship": {Beliefs: 1, Forefathers: 2, Ancestors: 2},
    Polytheism: {Deities: 3, Faith: 1, Gods: 1, Pantheon: 1},

    Dualism: {Religion: 3, Faith: 1, Cult: 1},
    Monotheism: {Religion: 1, Church: 1},
    "Non-theism": {Beliefs: 3, Spirits: 1},

    Cult: {Cult: 4, Sect: 4, Worship: 1, Orden: 1, Coterie: 1, Arcanum: 1},
    "Dark Cult": {Cult: 2, Sect: 2, Occultism: 1, Idols: 1, Coven: 1, Circle: 1, Blasphemy: 1},

    Heresy: {Heresy: 3, Sect: 2, Schism: 1, Dissenters: 1, Circle: 1, Brotherhood: 1, Society: 1, Iconoclasm: 1, Dissent: 1, Apostates: 1}
  };

  const generate = function () {
    TIME && console.time("generateReligions");
    const cells = pack.cells,
      states = pack.states,
      cultures = pack.cultures;
    const religions = (pack.religions = []);
    cells.religion = new Uint16Array(cells.culture); // cell religion; initially based on culture

    // add folk religions
    pack.cultures.forEach(c => {
      if (!c.i) {
        religions.push({i: 0, name: "No religion"});
        return;
      }
      if (c.removed) {
        religions.push({i: c.i, name: "Extinct religion for " + c.name, color: getMixedColor(c.color, 0.1, 0), removed: true});
        return;
      }
      const form = rw(forms.Folk);
      const name = c.name + " " + rw(types[form]);
      const deity = form === "Animism" ? null : getDeityName(c.i);
      const color = getMixedColor(c.color, 0.1, 0); // `url(#hatch${rand(8,13)})`;
      religions.push({i: c.i, name, color, culture: c.i, type: "Folk", form, deity, center: c.center, origin: 0});
    });

    if (religionsInput.value == 0 || pack.cultures.length < 2) {
      religions.filter(r => r.i).forEach(r => (r.code = abbreviate(r.name)));
      return;
    }

    const burgs = pack.burgs.filter(b => b.i && !b.removed);
    const sorted = burgs.length > +religionsInput.value ? burgs.sort((a, b) => b.population - a.population).map(b => b.cell) : cells.i.filter(i => cells.s[i] > 2).sort((a, b) => cells.s[b] - cells.s[a]);
    const religionsTree = d3.quadtree();
    const spacing = (graphWidth + graphHeight) / 6 / religionsInput.value; // base min distance between towns
    const cultsCount = Math.floor((rand(10, 40) / 100) * religionsInput.value);
    const count = +religionsInput.value - cultsCount + religions.length;

    // generate organized religions
    for (let i = 0; religions.length < count && i < 1000; i++) {
      let center = sorted[biased(0, sorted.length - 1, 5)]; // religion center
      const form = rw(forms.Organized);
      const state = cells.state[center];
      const culture = cells.culture[center];

      const deity = form === "Non-theism" ? null : getDeityName(culture);
      let [name, expansion] = getReligionName(form, deity, center);
      if (expansion === "state" && !state) expansion = "global";
      if (expansion === "culture" && !culture) expansion = "global";

      if (expansion === "state" && Math.random() > 0.5) center = states[state].center;
      if (expansion === "culture" && Math.random() > 0.5) center = cultures[culture].center;

      if (!cells.burg[center] && cells.c[center].some(c => cells.burg[c])) center = cells.c[center].find(c => cells.burg[c]);
      const x = cells.p[center][0],
        y = cells.p[center][1];

      const s = spacing * gauss(1, 0.3, 0.2, 2, 2); // randomize to make the placement not uniform
      if (religionsTree.find(x, y, s) !== undefined) continue; // to close to existing religion

      // add "Old" to name of the folk religion on this culture
      const folk = religions.find(r => r.culture === culture && r.type === "Folk");
      if (folk && expansion === "culture" && folk.name.slice(0, 3) !== "Old") folk.name = "Old " + folk.name;
      const origin = folk ? folk.i : 0;

      const expansionism = rand(3, 8);
      const color = getMixedColor(religions[origin].color, 0.3, 0); // `url(#hatch${rand(0,5)})`;
      religions.push({i: religions.length, name, color, culture, type: "Organized", form, deity, expansion, expansionism, center, origin});
      religionsTree.add([x, y]);
    }

    // generate cults
    for (let i = 0; religions.length < count + cultsCount && i < 1000; i++) {
      const form = rw(forms.Cult);
      let center = sorted[biased(0, sorted.length - 1, 1)]; // religion center
      if (!cells.burg[center] && cells.c[center].some(c => cells.burg[c])) center = cells.c[center].find(c => cells.burg[c]);
      const x = cells.p[center][0],
        y = cells.p[center][1];

      const s = spacing * gauss(2, 0.3, 1, 3, 2); // randomize to make the placement not uniform
      if (religionsTree.find(x, y, s) !== undefined) continue; // to close to existing religion

      const culture = cells.culture[center];
      const folk = religions.find(r => r.culture === culture && r.type === "Folk");
      const origin = folk ? folk.i : 0;
      const deity = getDeityName(culture);
      const name = getCultName(form, center);
      const expansionism = gauss(1.1, 0.5, 0, 5);
      const color = getMixedColor(cultures[culture].color, 0.5, 0); // "url(#hatch7)";
      religions.push({i: religions.length, name, color, culture, type: "Cult", form, deity, expansion: "global", expansionism, center, origin});
      religionsTree.add([x, y]);
      //debug.append("circle").attr("cx", x).attr("cy", y).attr("r", 2).attr("fill", "red");
    }

    expandReligions();

    // generate heresies
    religions
      .filter(r => r.type === "Organized")
      .forEach(r => {
        if (r.expansionism < 3) return;
        const count = gauss(0, 1, 0, 3);
        for (let i = 0; i < count; i++) {
          let center = ra(cells.i.filter(i => cells.religion[i] === r.i && cells.c[i].some(c => cells.religion[c] !== r.i)));
          if (!center) continue;
          if (!cells.burg[center] && cells.c[center].some(c => cells.burg[c])) center = cells.c[center].find(c => cells.burg[c]);
          const x = cells.p[center][0],
            y = cells.p[center][1];
          if (religionsTree.find(x, y, spacing / 10) !== undefined) continue; // to close to other

          const culture = cells.culture[center];
          const name = getCultName("Heresy", center);
          const expansionism = gauss(1.2, 0.5, 0, 5);
          const color = getMixedColor(r.color, 0.4, 0.2); // "url(#hatch6)";
          religions.push({i: religions.length, name, color, culture, type: "Heresy", form: r.form, deity: r.deity, expansion: "global", expansionism, center, origin: r.i});
          religionsTree.add([x, y]);
          //debug.append("circle").attr("cx", x).attr("cy", y).attr("r", 2).attr("fill", "green");
        }
      });

    expandHeresies();
    checkCenters();

    TIME && console.timeEnd("generateReligions");
  };

  const add = function (center) {
    const cells = pack.cells,
      religions = pack.religions;
    const r = cells.religion[center];
    const i = religions.length;
    const culture = cells.culture[center];
    const color = getMixedColor(religions[r].color, 0.3, 0);

    const type = religions[r].type === "Organized" ? rw({Organized: 4, Cult: 1, Heresy: 2}) : rw({Organized: 5, Cult: 2});
    const form = rw(forms[type]);
    const deity = type === "Heresy" ? religions[r].deity : form === "Non-theism" ? null : getDeityName(culture);

    let name, expansion;
    if (type === "Organized") [name, expansion] = getReligionName(form, deity, center);
    else {
      name = getCultName(form, center);
      expansion = "global";
    }
    const formName = type === "Heresy" ? religions[r].form : form;
    const code = abbreviate(
      name,
      religions.map(r => r.code)
    );
    religions.push({i, name, color, culture, type, form: formName, deity, expansion, expansionism: 0, center, cells: 0, area: 0, rural: 0, urban: 0, origin: r, code});
    cells.religion[center] = i;
  };

  // growth algorithm to assign cells to religions
  const expandReligions = function () {
    const cells = pack.cells,
      religions = pack.religions;
    const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
    const cost = [];

    religions
      .filter(r => r.type === "Organized" || r.type === "Cult")
      .forEach(r => {
        cells.religion[r.center] = r.i;
        queue.queue({e: r.center, p: 0, r: r.i, s: cells.state[r.center], c: r.culture});
        cost[r.center] = 1;
      });

    const neutral = (cells.i.length / 5000) * 200 * gauss(1, 0.3, 0.2, 2, 2) * neutralInput.value; // limit cost for organized religions growth
    const popCost = d3.max(cells.pop) / 3; // enougth population to spered religion without penalty

    while (queue.length) {
      const next = queue.dequeue(),
        n = next.e,
        p = next.p,
        r = next.r,
        c = next.c,
        s = next.s;
      const expansion = religions[r].expansion;

      cells.c[n].forEach(function (e) {
        if (expansion === "culture" && c !== cells.culture[e]) return;
        if (expansion === "state" && s !== cells.state[e]) return;

        const cultureCost = c !== cells.culture[e] ? 10 : 0;
        const stateCost = s !== cells.state[e] ? 10 : 0;
        const biomeCost = cells.road[e] ? 1 : biomesData.cost[cells.biome[e]];
        const populationCost = Math.max(rn(popCost - cells.pop[e]), 0);
        const heightCost = Math.max(cells.h[e], 20) - 20;
        const waterCost = cells.h[e] < 20 ? (cells.road[e] ? 50 : 1000) : 0;
        const totalCost = p + (cultureCost + stateCost + biomeCost + populationCost + heightCost + waterCost) / religions[r].expansionism;
        if (totalCost > neutral) return;

        if (!cost[e] || totalCost < cost[e]) {
          if (cells.h[e] >= 20 && cells.culture[e]) cells.religion[e] = r; // assign religion to cell
          cost[e] = totalCost;
          queue.queue({e, p: totalCost, r, c, s});
        }
      });
    }
  };

  // growth algorithm to assign cells to heresies
  const expandHeresies = function () {
    const cells = pack.cells,
      religions = pack.religions;
    const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
    const cost = [];

    religions
      .filter(r => r.type === "Heresy")
      .forEach(r => {
        const b = cells.religion[r.center]; // "base" religion id
        cells.religion[r.center] = r.i; // heresy id
        queue.queue({e: r.center, p: 0, r: r.i, b});
        cost[r.center] = 1;
      });

    const neutral = (cells.i.length / 5000) * 500 * neutralInput.value; // limit cost for heresies growth

    while (queue.length) {
      const next = queue.dequeue(),
        n = next.e,
        p = next.p,
        r = next.r,
        b = next.b;

      cells.c[n].forEach(function (e) {
        const religionCost = cells.religion[e] === b ? 0 : 2000;
        const biomeCost = cells.road[e] ? 0 : biomesData.cost[cells.biome[e]];
        const heightCost = Math.max(cells.h[e], 20) - 20;
        const waterCost = cells.h[e] < 20 ? (cells.road[e] ? 50 : 1000) : 0;
        const totalCost = p + (religionCost + biomeCost + heightCost + waterCost) / Math.max(religions[r].expansionism, 0.1);

        if (totalCost > neutral) return;

        if (!cost[e] || totalCost < cost[e]) {
          if (cells.h[e] >= 20 && cells.culture[e]) cells.religion[e] = r; // assign religion to cell
          cost[e] = totalCost;
          queue.queue({e, p: totalCost, r});
        }
      });
    }
  };

  function checkCenters() {
    const cells = pack.cells,
      religions = pack.religions;

    const codes = religions.map(r => r.code);
    religions
      .filter(r => r.i)
      .forEach(r => {
        r.code = abbreviate(r.name, codes);

        // move religion center if it's not within religion area after expansion
        if (cells.religion[r.center] === r.i) return; // in area
        const religCells = cells.i.filter(i => cells.religion[i] === r.i);
        if (!religCells.length) return; // extinct religion
        r.center = religCells.sort((a, b) => b.pop - a.pop)[0];
      });
  }

  function updateCultures() {
    TIME && console.time("updateCulturesForReligions");
    pack.religions = pack.religions.map((religion, index) => {
      if (index === 0) {
        return religion;
      }
      return {...religion, culture: pack.cells.culture[religion.center]};
    });
    TIME && console.timeEnd("updateCulturesForReligions");
  }

  // get supreme deity name
  const getDeityName = function (culture) {
    if (culture === undefined) {
      ERROR && console.error("Please define a culture");
      return;
    }
    const meaning = generateMeaning();
    const cultureName = Names.getCulture(culture, null, null, "", 0.8);
    return cultureName + ", The " + meaning;
  };

  function generateMeaning() {
    const a = ra(approaches); // select generation approach
    if (a === "Number") return ra(base.number);
    if (a === "Being") return ra(base.being);
    if (a === "Adjective") return ra(base.adjective);
    if (a === "Color + Animal") return ra(base.color) + " " + ra(base.animal);
    if (a === "Adjective + Animal") return ra(base.adjective) + " " + ra(base.animal);
    if (a === "Adjective + Being") return ra(base.adjective) + " " + ra(base.being);
    if (a === "Adjective + Genitive") return ra(base.adjective) + " " + ra(base.genitive);
    if (a === "Color + Being") return ra(base.color) + " " + ra(base.being);
    if (a === "Color + Genitive") return ra(base.color) + " " + ra(base.genitive);
    if (a === "Being + of + Genitive") return ra(base.being) + " of " + ra(base.genitive);
    if (a === "Being + of the + Genitive") return ra(base.being) + " of the " + ra(base.theGenitive);
    if (a === "Animal + of + Genitive") return ra(base.animal) + " of " + ra(base.genitive);
    if (a === "Adjective + Being + of + Genitive") return ra(base.adjective) + " " + ra(base.being) + " of " + ra(base.genitive);
    if (a === "Adjective + Animal + of + Genitive") return ra(base.adjective) + " " + ra(base.animal) + " of " + ra(base.genitive);
  }

  function getReligionName(form, deity, center) {
    const cells = pack.cells;
    const random = function () {
      return Names.getCulture(cells.culture[center], null, null, "", 0);
    };
    const type = function () {
      return rw(types[form]);
    };
    const supreme = function () {
      return deity.split(/[ ,]+/)[0];
    };
    const place = function (adj) {
      const base = cells.burg[center] ? pack.burgs[cells.burg[center]].name : pack.states[cells.state[center]].name;
      let name = trimVowels(base.split(/[ ,]+/)[0]);
      return adj ? getAdjective(name) : name;
    };
    const culture = function () {
      return pack.cultures[cells.culture[center]].name;
    };

    const m = rw(methods);
    if (m === "Random + type") return [random() + " " + type(), "global"];
    if (m === "Random + ism") return [trimVowels(random()) + "ism", "global"];
    if (m === "Supreme + ism" && deity) return [trimVowels(supreme()) + "ism", "global"];
    if (m === "Faith of + Supreme" && deity) return [ra(["Faith", "Way", "Path", "Word", "Witnesses"]) + " of " + supreme(), "global"];
    if (m === "Place + ism") return [place() + "ism", "state"];
    if (m === "Culture + ism") return [trimVowels(culture()) + "ism", "culture"];
    if (m === "Place + ian + type") return [place("adj") + " " + type(), "state"];
    if (m === "Culture + type") return [culture() + " " + type(), "culture"];
    return [trimVowels(random()) + "ism", "global"]; // else
  }

  function getCultName(form, center) {
    const cells = pack.cells;
    const type = function () {
      return rw(types[form]);
    };
    const random = function () {
      return trimVowels(Names.getCulture(cells.culture[center], null, null, "", 0).split(/[ ,]+/)[0]);
    };
    const burg = function () {
      return trimVowels(pack.burgs[cells.burg[center]].name.split(/[ ,]+/)[0]);
    };
    if (cells.burg[center]) return burg() + "ian " + type();
    if (Math.random() > 0.5) return random() + "ian " + type();
    return type() + " of the " + generateMeaning();
  }

  return {generate, add, getDeityName, expandReligions, updateCultures};
})();
