import {LiturgyData} from "../data/miracle/liturgydata.mjs";
import {BlessingDataModel} from "../data/blessing.mjs";
import {Blessing} from "../documents/blessing.mjs";

let months = [
    "Praios",
    "Rondra",
    "Efferd",
    "Travia",
    "Boron",
    "Hesinde",
    "Firun",
    "Tsa",
    "Phex",
    "Peraine",
    "Ingerimm",
    "Rahja",
    "Namenloser Tag"
]


/**
 * Imports a character from a file created in the tool Helden-Software
 * @param actorId the actor-id of the character
 * @param file the file from which the character should be imported
 */
export async function importCharacter(actorId, file) {
    let actor = game.actors.get(actorId)
    let xmlString = await parseFileToString(file)
    let domParser = new DOMParser()
    let dom = domParser.parseFromString(xmlString, 'application/xml')

    let rawJson = getJsonFromXML(dom)
    let characterJson = mapRawJson(actor, rawJson)

    actor.update(characterJson)
}

/**
 *
 * @param dom the XML-Dom from which the json should be extracted
 * @returns {{}} the json parsed from the xml
 */
function getJsonFromXML(dom) {
    let children = [...dom.children];

    // initializing object to be returned.
    let jsonResult = {};

    let attributes = dom.attributes ? dom.attributes : []
    for (let attribute of attributes) {
        jsonResult[attribute.name] = attribute.value
    }

    if (children.length) {
        for (let child of children) {

            // checking is child has siblings of same name.
            let childIsArray = children.filter(eachChild => eachChild.nodeName === child.nodeName).length > 1;

            // if child is array, save the values as array, else as strings.
            if (childIsArray) {
                if (jsonResult[child.nodeName] === undefined) {
                    jsonResult[child.nodeName] = [getJsonFromXML(child)];
                } else {
                    jsonResult[child.nodeName].push(getJsonFromXML(child));
                }
            } else {
                jsonResult[child.nodeName] = getJsonFromXML(child);
            }
        }
    }

    return jsonResult;
}

async function addSkillFromCompendiumByNameToActor(talentName, taw, actor) {
    const compendiumOfSkills = game.packs.get('DSA_4-1.talente');
    const talentId = compendiumOfSkills.index.find(skill => skill.name === talentName)
    if (talentId) {

        const talent = await compendiumOfSkills.getDocument(talentId._id);

        try {
            const embeddedDocument = (await actor.createEmbeddedDocuments('Item', [talent]))[0]
            embeddedDocument.update({system: {taw: taw}});
        } catch (error) {
            console.error(`${talentName} not found in items`, error)
        }
    }
}

async function addAdvantageFromCompendiumByNameToActor(advantageName, advantageValue, actor) {
    const compendiumOfAdvantages = game.packs.get('DSA_4-1.Advantage');
    const advantageId = compendiumOfAdvantages.index.find(skill => skill.name === advantageName)
    if (advantageId) {

        const advantage = await compendiumOfAdvantages.getDocument(advantageId._id);

        try {
            const embeddedDocument = (await actor.createEmbeddedDocuments('Item', [advantage]))[0]
            embeddedDocument.update({system: {value: advantageValue}});
        } catch (error) {
            console.error(`${advantageName} not found in items`, error)
        }
    }
}

async function addSpellsFromCompendiumByNameToActor(spellName, zfw, representation, hauszauber, actor) {
    const compendiumOfSpells = game.packs.get('DSA_4-1.spells');
    const SCREAMING_NAME = spellName.toUpperCase()
    const spellId = compendiumOfSpells.index.find(spell => spell.name === SCREAMING_NAME)
    if (spellId) {

        const spell = await compendiumOfSpells.getDocument(spellId._id);

        try {
            const embeddedDocument = (await actor.createEmbeddedDocuments('Item', [spell]))[0]
            embeddedDocument.update({system: {zfw: zfw, hauszauber: hauszauber, repräsentation: representation}});
        } catch (error) {
            console.error(`${spell} not found in items`, error)
        }
    }
}

async function addLiturgiesFromCompendiumByNameToActor(liturgyName, actor) {
    const compendiumOfLiturgies = game.packs.get('DSA_4-1.liturgien');
    const liturgyId = compendiumOfLiturgies.index.find(liturgy => {
        return liturgy.name === LiturgyData.lookupAlias(liturgyName.split(" (")[0])
    })
    if (liturgyId) {

        const liturgy = await compendiumOfLiturgies.getDocument(liturgyId._id);

        try {
            await actor.createEmbeddedDocuments('Item', [liturgy])
        } catch (error) {
            console.error(`${liturgy} not found in items`, error)
        }
    }
}

/**
 * gets the text content of a file
 * @param file the file with the desired content
 * @returns {Promise<String>} a promise that returns the string contents of the file
 */
async function parseFileToString(file) {
    return new Promise((resolve, reject) => {
        let reader = new FileReader()
        reader.readAsText(file, "utf-8")
        reader.onload = event => {
            resolve(event.target.result)
        }
        reader.onerror = event => {
            reject(event)
        }
    })
}

/**
 *Calculates a Birthdate String in the Calendar of Bosparans Fall
 * @param json json with the day, the month and the year of the birthday
 * @returns {string} a string in the format of "DD.MMMM.YYYY BF"
 */
function calculateBirthdate(json) {
    let day = json.gbtag
    let month = months[parseInt(json.gbmonat) - 1]
    let year = json.gbjahr

    return `${day}. ${month} ${year} BF`
}

function mapSkills(actor, held) {
    for (let talent in held.talentliste.talent) {
        talent = held.talentliste.talent[talent]

        // hook liturgy
        if (talent.name.startsWith("Liturgiekenntnis")) {

            actor.createEmbeddedDocuments('Item', [
                new Blessing({
                    name: talent.name,
                    type: "Blessing",
                    system: {
                        gottheit: new RegExp("\\((.+)\\)").exec(talent.name)[1],
                        wert: talent.value
                    }
                })
            ])

        } else {
            // proceed
            addSkillFromCompendiumByNameToActor(talent.name, talent.value, actor)
        }
    }
}

function mapAdvantages(actor, held) {
    for (let advantage in held.vt.vorteil) {
        advantage = held.vt.vorteil[advantage]
        addAdvantageFromCompendiumByNameToActor(advantage.name, advantage.value, actor)
    }
}

function mapSpells(actor, held) {
    for (let spell in held.zauberliste.zauber) {
        spell = held.zauberliste.zauber[spell]
        addSpellsFromCompendiumByNameToActor(spell.name, spell.value, spell.repraesentation, spell.hauszauber === "true", actor)
    }
}

function mapMiracles(actor, liturgies) {
    for (let liturgy in liturgies) {
        liturgy = liturgies[liturgy]
        addLiturgiesFromCompendiumByNameToActor(liturgy.name, actor)
    }
}

/**
 * parses a json into a fitting character-json
 * @param rawJson the json parsed from the Helden-Software XML
 * @returns {{}} a json representation of the character
 */
function mapRawJson(actor, rawJson) {
    let json = {}
    let held = rawJson.helden.held;
    json.name = held.name
    json.meta = {}
    json.meta.spezies = held.basis.rasse.string
    json.meta.kultur = held.basis.kultur.string
    let professions = []
    for (let profession in held.basis.ausbildungen.ausbildung) {
        profession = held.basis.ausbildungen.ausbildung[profession]
        if (profession.tarnidentitaet) {
            professions = [profession.tarnidentitaet]
            break;
        }
        let professionString = profession.string
        professions.push(professionString)
    }
    json.meta.professions = professions
    json.meta.geschlecht = held.basis.geschlecht.name
    json.meta.haarfarbe = held.basis.rasse.aussehen.haarfarbe
    json.meta.groesse = held.basis.rasse.groesse.value
    json.meta.augenfarbe = held.basis.rasse.aussehen.augenfarbe
    json.meta.geburtstag = calculateBirthdate(held.basis.rasse.aussehen)
    json.meta.alter = held.basis.rasse.aussehen.alter
    json.meta.gewicht = held.basis.rasse.groesse.gewicht
    json.meta.aussehen = [
        held.basis.rasse.aussehen.aussehentext0,
        held.basis.rasse.aussehen.aussehentext1,
        held.basis.rasse.aussehen.aussehentext2,
        held.basis.rasse.aussehen.aussehentext3].join('\n')
    json.meta.familie = [
        held.basis.rasse.aussehen.familietext0,
        held.basis.rasse.aussehen.familietext1,
        held.basis.rasse.aussehen.familietext2,
        held.basis.rasse.aussehen.familietext3,
        held.basis.rasse.aussehen.familietext4,
        held.basis.rasse.aussehen.familietext5].join('\n')
    json.meta.titel = held.basis.rasse.aussehen.titel
    json.meta.stand = held.basis.rasse.aussehen.stand
    let attributes = held.eigenschaften.eigenschaft
    json.attribute = {}
    if (held.basis.gilde) {
        json.attribute.gilde = held.basis.gilde.name
    }
    json.attribute.mu = getAttributeJson(attributes, "Mut")
    json.attribute.kl = getAttributeJson(attributes, "Klugheit")
    json.attribute.in = getAttributeJson(attributes, "Intuition")
    json.attribute.ch = getAttributeJson(attributes, "Charisma")
    json.attribute.ff = getAttributeJson(attributes, "Fingerfertigkeit")
    json.attribute.ge = getAttributeJson(attributes, "Gewandtheit")
    json.attribute.ko = getAttributeJson(attributes, "Konstitution")
    json.attribute.kk = getAttributeJson(attributes, "Körperkraft")
    json.mr = {
        mod: filterAttribute(attributes, "Magieresistenz").mod
    }
    json.lep = {
        mod: filterAttribute(attributes, "Lebensenergie").mod
    }
    json.aup = {
        mod: filterAttribute(attributes, "Ausdauer").mod
    }
    json.asp = {
        mod: filterAttribute(attributes, "Astralenergie").mod
    }
    json.kap = {
        mod: filterAttribute(attributes, "Karmaenergie").mod
    }
    let attribute = filterAttribute(attributes, "Karmaenergie")
    attribute = filterAttribute(attributes, "ini")
    json.ini = {
        mod: attribute.mod,
        aktuell: attribute.value
    }
    json.attribute.so = getAttributeJson(attributes, "Sozialstatus")
    mapAdvantages(actor, held)
    let specialAbilities = []
    let liturgies = []
    for (let specialAbility in held.sf.sonderfertigkeit) {
        specialAbility = held.sf.sonderfertigkeit[specialAbility]
        if (specialAbility.name.startsWith("Liturgie:")) {
            liturgies.push({
                name: specialAbility.name.replace("Liturgie:", "").trim(),
            })
        } else {
            let specialAbilityJson = {
                name: specialAbility.name,
                auswahlen: []
            }
            let fields = Object.keys(specialAbility)
            if (fields.length > 1) {
                for (let field in fields) {
                    field = fields[field]
                    if (field !== "name") {
                        let choices = specialAbility[field]
                        if (choices.hasOwnProperty("name")) {
                            specialAbilityJson.auswahlen.push(choices.name)
                        } else {
                            for (let choice in choices.wahl) {
                                choice = choices.wahl[choice]
                                specialAbilityJson.auswahlen.push(choice.value)
                            }
                        }
                    }
                }
            }
            specialAbilities.push(specialAbilityJson)
        }
    }
    json.sonderfertigkeiten = specialAbilities
    json.liturgien = liturgies

    mapSkills(actor, held)
    mapSpells(actor, held)
    mapMiracles(actor, liturgies)

    let combatValues = []
    for (let combatValue in held.kampf.kampfwerte) {
        combatValue = held.kampf.kampfwerte[combatValue]
        combatValues.push({
            name: combatValue.name,
            at: combatValue.attacke.value,
            pa: combatValue.parade.value,
        })
    }
    json.kampfwerte = combatValues
    let notes = []
    for (let note in held.kommentare) {
        note = held.kommentare[note]
        if (note.hasOwnProperty("key")) {
            notes.push({
                key: note.key,
                notiz: note.kommentar,
            })
        }
    }
    json.notizen = notes

    return {
        name: held.name,
        system: json,
    }
}

/**
 *
 * @param attributes an array with the attributes
 * @param name the name of the chosen attribute
 * @returns {{}} a representation of the chosen Attribute
 */
function getAttributeJson(attributes, name) {
    let attribute = filterAttribute(attributes, name)
    return {
        start: parseInt(attribute.startwert),
        aktuell: parseInt(attribute.value),
        mod: parseInt(attribute.mod),
    }
}

/**
 * filters a given attribute array based on the name of an attribute
 * @param attributes the attribute array
 * @param name the name of the desired attribute
 * @returns {{}} the json of the desired attribute
 */
function filterAttribute(attributes, name) {
    return attributes.filter(attribute => attribute.name === name)[0]
}

Hooks.on("getActorContextOptions", (application, menuItems) => {
    menuItems.push({
        name: "Import from XML",
        icon: '<i class="fas fa-file"></i>',
        callback: (li) => {
            const actorId = li.getAttribute("data-entry-id")
            const actor = game.actors.get(actorId)
            actor.import()
        }
    })
})
