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

package engine.wpak;

import engine.gameManager.ConfigManager;
import engine.mbEnums;
import engine.util.Hasher;
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());
            WpakPowerManager.powers.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;
            int blah = 0;

            if (powerEntry.power_id.equals("ASS-BACKSTAB"))
                blah = 1;

                switch (key) {
                    case "ACTION":
                        actionEntry = new ActionEntry();
                        arguments = Arrays.asList(lineValues.get(1).trim().split("\\s+"));
                        actionEntry.effect_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 = 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("\"", "");
                            equipmentPreReq.required = Integer.parseInt(argumentIterator.next());
                            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);
                }
            Logger.info("POWER: " + powerEntry.power_id + " " + key);
        }

        // 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;
    }

}