// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
//      Magicbane Emulator Project © 2013 - 2024
//                www.magicbane.com

package engine.wpak;

import engine.gameManager.ConfigManager;
import engine.mbEnums;
import engine.wpak.data.*;
import org.pmw.tinylog.Logger;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PowersParser {

    private static final Pattern POWER_REGEX = Pattern.compile("(?<=POWERBEGIN)(.+?)(?=POWEREND)", Pattern.DOTALL);
    private static final Pattern STRSPLIT_REGEX = Pattern.compile("([^\"]\\S*|\"[^\"]*\")\\s*");
    private static final Pattern CONDITION_REGEX = Pattern.compile("(?<=CONDITIONBEGIN)(.+?)(?=CONDITIONEND)", Pattern.DOTALL);
    private static final String powersPath = ConfigManager.DEFAULT_DATA_DIR + "wpak/Powers.cfg";

    public static void parseWpakFile() {

        // Read .wpak file from disk

        byte[] fileData;

        try {
            fileData = Files.readAllBytes(Paths.get(powersPath));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        String fileContents = new String(fileData);

        // Iterate over power entries from .wpak data

        Matcher matcher = POWER_REGEX.matcher(fileContents);

        while (matcher.find()) {

            Power power = parsePowerEntry(matcher.group().trim());
            // @TODO WpakPowerManager._powersLookup.put(Hasher.SBStringHash(power.power_id), power);
        }
    }

    private static Power parsePowerEntry(String powerData) {

        Power powerEntry = new Power();
        StringBuilder conditionBuilder = new StringBuilder();
        StringBuilder powerBuilder = new StringBuilder();
        String conditionString;
        String powerString;
        java.util.Iterator<String> iterator;
        java.util.Iterator<String> argumentIterator;
        int endPos = 0;

        // Separate out any conditions from the power data

        Matcher matcher = CONDITION_REGEX.matcher(powerData);

        while (matcher.find()) {
            conditionBuilder.append(matcher.group().trim());
            powerBuilder.append(powerData, endPos, matcher.start());
            endPos = matcher.end();
        }

        powerBuilder.append(powerData.substring(endPos));

        // Cleanup dangling tags and lines that contain a # and leading/trailing blank lines

        powerString = powerBuilder.toString().replaceAll("CONDITIONBEGINCONDITIONEND", "")
                .replaceAll("(?m)^(\\s*#.*|\\s*)\r?\n?", "");
        conditionString = conditionBuilder.toString().replaceAll("(?m)^(\\s*#.*|\\s*)\r?\n?", "");

        // Parse header line in power data

        List<String> lineData = Arrays.asList(powerString.trim().split("\n"));
        List<String> powerHeader = new ArrayList<>();

        String headerString = lineData.get(0);
        headerString = headerString.replace("\n", " ");

        matcher = STRSPLIT_REGEX.matcher(headerString);

        while (matcher.find())
            powerHeader.add(matcher.group().trim());

        iterator = powerHeader.iterator();

        powerEntry.power_id = iterator.next();
        powerEntry.power = iterator.next().replaceAll("\"", "");

        PowerEntry power = new PowerEntry();
        power.power_type = mbEnums.PowerType.valueOf(iterator.next());
        power.icon = Integer.parseInt(iterator.next());
        power.focusLine = iterator.next().replaceAll("\"", "");
        powerEntry.powers.add(power);

        String nextValue = iterator.next();

        // Account for second definition

        if (nextValue.equals("SPELL") || nextValue.equals("SKILL")) {
            power = new PowerEntry();
            power.power_type = mbEnums.PowerType.valueOf(nextValue);
            power.icon = Integer.parseInt(iterator.next());
            power.focusLine = iterator.next().replaceAll("\"", "");
            powerEntry.powers.add(power);
            powerEntry.target_type = mbEnums.PowerTargetType.valueOf(iterator.next());
        } else
            powerEntry.target_type = mbEnums.PowerTargetType.valueOf(nextValue);

        powerEntry.range = Integer.parseInt(iterator.next());
        powerEntry.areaType = mbEnums.AreaType.valueOf(iterator.next());
        powerEntry.areaRange = Integer.parseInt(iterator.next());
        powerEntry.excludeType = mbEnums.ExcludeType.valueOf(iterator.next());
        powerEntry.costType = mbEnums.CostType.valueOf(iterator.next());
        powerEntry.cost = Float.parseFloat(iterator.next());
        powerEntry.difficulty = Float.parseFloat(iterator.next());
        powerEntry.precision = Float.parseFloat(iterator.next());
        // Cleanup init_time in client data which is 0.35.1 or some such
        powerEntry.init_time = Float.parseFloat(iterator.next().replaceAll("(\\.0)+$", ""));
        powerEntry.release_time = Float.parseFloat(iterator.next());
        powerEntry.recycle_time = Float.parseFloat(iterator.next());
        powerEntry.hitRollYN = Integer.parseInt(iterator.next());
        powerEntry.castingMode = mbEnums.CastingModeType.valueOf(iterator.next());
        powerEntry.initAmin = Integer.parseInt(iterator.next());
        powerEntry.releaseAnim = Integer.parseInt(iterator.next());
        powerEntry.targetSelect = mbEnums.TargetSelectType.valueOf(iterator.next());

        // Process key value pairs after header

        iterator = lineData.iterator();
        iterator.next(); // Ignore header

        while (iterator.hasNext()) {

            String lineValue = iterator.next();
            List<String> lineValues = Arrays.asList(lineValue.split("="));
            String key = lineValues.get(0).trim();
            ActionEntry actionEntry;
            List<String> arguments;
            Matcher argumentMatcher;

            switch (key) {
                case "ACTION":
                    actionEntry = new ActionEntry();
                    arguments = Arrays.asList(lineValues.get(1).trim().split("\\s+"));
                    actionEntry.action_id = arguments.get(0);
                    actionEntry.minTrains = Integer.parseInt(arguments.get(1));
                    actionEntry.maxTrains = Integer.parseInt(arguments.get(2));
                    actionEntry.duration = Float.parseFloat(arguments.get(3));
                    actionEntry.curve = mbEnums.CompoundCurveType.valueOf(arguments.get(4));
                    actionEntry.stackingCategory = arguments.get(5);
                    actionEntry.stackingPriority = Integer.parseInt(arguments.get(6));
                    actionEntry.categoryToPower = mbEnums.CategoryToPowerType.valueOf(arguments.get(7));
                    powerEntry.actionEntries.add(actionEntry);
                    break;
                case "MaxLevel":
                    powerEntry.maxLevel = Integer.parseInt(lineValues.get(1).trim());
                    break;
                case "HateValue":
                    arguments = Arrays.asList(lineValues.get(1).trim().split("\\s+"));
                    powerEntry.hateValue = Integer.parseInt(arguments.get(0));

                    // Not all entries have a curve.  Defaults to DefaultFlat;

                    if (arguments.size() > 1)
                        powerEntry.hateCurve = mbEnums.CompoundCurveType.valueOf(arguments.get(1));
                    break;
                case "LOOPANIMID":
                    powerEntry.loopAnimID = Integer.parseInt(lineValues.get(1).trim());
                    break;
                case "GRANTOVERRIDEVAR":
                    powerEntry.grantOverrideVar = lineValues.get(1).trim();
                    break;
                case "DESCRIPTION":
                    powerEntry.description.add(lineValues.get(1).trim());
                    break;
                case "CATEGORY":
                    powerEntry.category = mbEnums.PowerCategoryType.valueOf(lineValues.get(1).trim());
                    break;
                case "CURVE":
                    arguments = Arrays.asList(lineValues.get(1).trim().split("\\s+"));
                    powerEntry.curves.put(arguments.get(0), mbEnums.CompoundCurveType.valueOf(arguments.get(1)));
                    break;
                case "EQPREREQ":
                    argumentMatcher = STRSPLIT_REGEX.matcher(lineValues.get(1).trim());
                    arguments = new ArrayList<>();

                    while (argumentMatcher.find())
                        arguments.add(argumentMatcher.group().trim());

                    argumentIterator = arguments.iterator();

                    while (argumentIterator.hasNext()) {
                        EquipmentPreReq equipmentPreReq = new EquipmentPreReq();
                        equipmentPreReq.slot = mbEnums.EquipSlotType.valueOf(argumentIterator.next());
                        equipmentPreReq.skill = argumentIterator.next().replaceAll("\"", "");

                        if (argumentIterator.hasNext())
                            equipmentPreReq.required = Integer.parseInt(argumentIterator.next());
                        else
                            equipmentPreReq.required = 0;

                        powerEntry.equipmentPreReq.add(equipmentPreReq);
                    }
                    break;
                case "CANCASTWHILEMOVING":
                    powerEntry.canCastWhileMoving = Boolean.parseBoolean(lineValues.get(1).trim());
                    break;
                case "CANCASTWHILEFLYING":
                    powerEntry.canCastWhileFlying = Boolean.parseBoolean(lineValues.get(1).trim());
                    break;
                case "BLADETRAILS":
                    powerEntry.bladeTrails = Boolean.parseBoolean(lineValues.get(1).trim());
                    break;
                case "EFFECTPREREQ":
                    Effect effectPreReq = new Effect();
                    arguments = Arrays.asList(lineValues.get(1).trim().split("\\s+"));
                    effectPreReq.effect_id = arguments.get(9);
                    effectPreReq.level = Integer.parseInt(arguments.get(1));
                    effectPreReq.message = arguments.get(2);
                    powerEntry.effectPreReqs.add(effectPreReq);
                    break;
                case "MONSTERTYPERESTRICTS":
                    arguments = Arrays.asList(lineValues.get(1).trim().split("\\s+"));

                    for (String restriction : arguments)
                        powerEntry.monsterRestricts.add(mbEnums.MonsterType.valueOf(restriction.trim()));
                    break;
                case "MONSTERTYPEPREREQS":
                    arguments = Arrays.asList(lineValues.get(1).trim().split("\\s+"));

                    for (String restriction : arguments)
                        powerEntry.monsterPrereqs.add(mbEnums.MonsterType.valueOf(restriction.trim()));
                    break;
                case "SHOULDCHECKPATH":
                    powerEntry.shouldCheckPath = Boolean.parseBoolean(lineValues.get(1).trim());
                    break;
                case "STICKY":
                    powerEntry.sticky = Boolean.parseBoolean(lineValues.get(1).trim());
                    break;
                case "PULSEINFO":
                    arguments = Arrays.asList(lineValues.get(1).trim().split("\\s+"));
                    powerEntry.pulseCycle = Integer.parseInt(arguments.get(0));
                    powerEntry.pulseDuration = Integer.parseInt(arguments.get(1));
                    break;
                case "MAXNUMMOBTARGETS":
                    powerEntry.maxMobTargets = Integer.parseInt(lineValues.get(1).trim());
                    break;
                case "MAXNUMPLAYERTARGETS":
                    powerEntry.maxPlayerTargets = Integer.parseInt(lineValues.get(1).trim());
                    break;
                case "ISADMINPOWER":
                    powerEntry.isAdminPower = Boolean.parseBoolean(lineValues.get(1).trim());
                    break;
                case "ISPROJECTILE":
                    powerEntry.isProjectile = Boolean.parseBoolean(lineValues.get(1).trim());
                    break;
                case "CASTERSPULSEPARTICLE":
                    powerEntry.casterPulseParticle = Integer.parseInt(lineValues.get(1).trim());
                    break;
                case "TARGETEFFECTPREREQS_ORED":
                    Effect preReq = new Effect();
                    arguments = Arrays.asList(lineValues.get(1).trim().split("\\s+"));
                    preReq.effect_id = arguments.get(0);
                    preReq.level = Integer.parseInt(arguments.get(1));
                    powerEntry.targetEffectPrereqs.add(preReq);
                    break;
                case "SOUNDS":   // Values not parsed
                case "APPLYDAMAGESELF":
                case "APPLYDAMAGECASTER":
                case "APPLYDAMAGEOTHER":
                case "APPLYDAMAGETARGET":
                case "APPLYEFFECTSELF":
                case "APPLYEFFECTOTHER":
                case "APPLYEFFECTCASTER":
                case "APPLYEFFECTTARGET":
                case "FIZZLEOTHER":
                case "FIZZLESELF":
                case "INITSTRING":
                case "SUCCESSOTHER":
                case "SUCCESSSELF":
                case "WEAROFFEFFECTOTHER":
                case "WEAROFFEFFECTSELF":
                    break;
                default:
                    Logger.error("Unhandled variable type:" + key + " for power: " + powerEntry.power_id);
            }
        }

        // Parse power conditions

        if (conditionString.isEmpty() == false) {

            List<String> conditions = Arrays.asList(conditionString.split("\n"));

            for (String condition : conditions) {
                List<String> parameters = Arrays.asList(condition.trim().split("\\s+"));
                powerEntry.conditions.put(parameters.get(0), Float.parseFloat(parameters.get(1)));
            }
        }
        return powerEntry;
    }

}

