// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
//      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.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()) {

            PowerEntry powerEntry = parsePowerEntry(matcher.group().trim());

        }
    }

    private static PowerEntry parsePowerEntry(String powerData) {

        PowerEntry powerEntry = new PowerEntry();

        try {

            StringBuilder conditionString = new StringBuilder();
            StringBuilder powerString = new StringBuilder();

            int endPos = 0;

            // Separate out any conditions from the power data

            Matcher matcher = CONDITION_REGEX.matcher(powerData);

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

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

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

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

            conditionString = new StringBuilder(conditionString.toString().replaceAll("(?m)^(\\s*#.*|\\s*)\r?\n?", ""));

            // Parse header line in power data

            String[] lineData = powerString.toString().trim().split("\n");
            ArrayList<String> powerHeader = new ArrayList<>();

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

            matcher = STRSPLIT_REGEX.matcher(headerString);

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

            java.util.Iterator<String> iterator = powerHeader.iterator();

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

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

            String nextValue = iterator.next();

            // Account for second definition

            if (nextValue.equals("SPELL") || nextValue.equals("SKILL")) {
                power = new PowerData();
                power.power_type = mbEnums.PowerType.valueOf(nextValue);
                power.icon = Integer.parseInt(iterator.next());
                power.powerBase = 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());
            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 = Arrays.stream(lineData).iterator();
            iterator.next(); // Ignore header

            while (iterator.hasNext()) {

                String lineValue = iterator.next();
                String[] lineValues = lineValue.split("=");
                String key = lineValues[0].trim();
                ActionEntry actionEntry;
                String[] arguments;
                Matcher matcher1;
                ArrayList<String> args;

                switch (key) {
                    case "ACTION":
                        actionEntry = new ActionEntry();
                        arguments = lineValues[1].trim().split("\\s+");

                        if (powerEntry.power_id.equals("HNT-050"))
                            Logger.error("debug");

                        actionEntry.effect_id = arguments[0];
                        actionEntry.minTrains = Integer.parseInt(arguments[1]);
                        actionEntry.maxTrains = Integer.parseInt(arguments[2]);
                        actionEntry.duration = Float.parseFloat(arguments[3]);
                        actionEntry.curve = mbEnums.CompoundCurveType.valueOf(arguments[4]);
                        actionEntry.stackingCategory = arguments[5];
                        actionEntry.stackingPriority = Integer.parseInt(arguments[6]);
                        actionEntry.categoryToPower = mbEnums.CategoryToPowerType.valueOf(arguments[7]);
                        powerEntry.actionEntries.add(actionEntry);
                        break;
                    case "MaxLevel":
                        powerEntry.maxLevel = Integer.parseInt(lineValues[1].trim());
                        break;
                    case "HateValue":
                        arguments = lineValues[1].trim().split("\\s+");
                        powerEntry.hateValue = Integer.parseInt(arguments[0]);

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

                        if (arguments.length > 1)
                            powerEntry.hateCurve = mbEnums.CompoundCurveType.valueOf(arguments[1]);
                        break;
                    case "LOOPANIMID":
                        powerEntry.loopAnimID = Integer.parseInt(lineValues[1].trim());
                        break;
                    case "GRANTOVERRIDEVAR":
                        powerEntry.grantOverrideVar = lineValues[1].trim();
                        break;
                    case "DESCRIPTION":
                        powerEntry.description.add(lineValues[1].trim());
                        break;
                    case "CATEGORY":
                        powerEntry.category = lineValues[1].trim();
                        break;
                    case "CURVE":
                        arguments = lineValues[1].trim().split("\\s+");
                        powerEntry.curves.put(arguments[0], mbEnums.CompoundCurveType.valueOf(arguments[1]));
                        break;
                    case "EQPREREQ":
                        EquipmentPreReq equipmentPreReq = new EquipmentPreReq();
                        matcher1 = STRSPLIT_REGEX.matcher(lineValues[1].trim());
                        args = new ArrayList<>();

                        while (matcher1.find())
                            args.add(matcher1.group().trim());

                        equipmentPreReq.slot = mbEnums.EquipSlotType.valueOf(args.get(0));
                        equipmentPreReq.skill = args.get(1).replaceAll("\"", "");
                        equipmentPreReq.level = Integer.parseInt(args.get(2));
                        powerEntry.equipmentPreReq = equipmentPreReq;
                        break;
                    case "CANCASTWHILEMOVING":
                        powerEntry.canCastWhileMoving = Boolean.parseBoolean(lineValues[1].trim());
                        break;
                    case "CANCASTWHILEFLYING":
                        powerEntry.canCastWhileFlying = Boolean.parseBoolean(lineValues[1].trim());
                        break;
                    case "BLADETRAILS":
                        powerEntry.bladeTrails = Boolean.parseBoolean(lineValues[1].trim());
                        break;
                    case "EFFECTPREREQ":
                        EffectDescription effectPreReq = new EffectDescription();
                        arguments = lineValues[1].trim().split("\\s+");
                        effectPreReq.effect_id = arguments[9];
                        effectPreReq.level = Integer.parseInt(arguments[1]);
                        effectPreReq.message = arguments[2];
                        powerEntry.effectPreReqs.add(effectPreReq);
                        break;
                    case "MONSTERTYPERESTRICTS":
                        arguments = lineValues[1].trim().split("\\s+");
                        for (String restriction : arguments)
                            powerEntry.monsterRestricts.add(mbEnums.MonsterType.valueOf(restriction.trim()));
                        break;
                    case "MONSTERTYPEPREREQS":
                        arguments = lineValues[1].trim().split("\\s+");
                        for (String restriction : arguments)
                            powerEntry.monsterPrereqs.add(mbEnums.MonsterType.valueOf(restriction.trim()));
                        break;
                    case "SHOULDCHECKPATH":
                        powerEntry.shouldCheckPath = Boolean.parseBoolean(lineValues[1].trim());
                        break;
                    case "STICKY":
                        powerEntry.sticky = Boolean.parseBoolean(lineValues[1].trim());
                        break;
                    case "PULSEINFO":
                        arguments = lineValues[1].trim().split("\\s+");
                        powerEntry.pulseCycle = Integer.parseInt(arguments[0]);
                        powerEntry.pulseDuration = Integer.parseInt(arguments[1]);
                        break;
                    case "MAXNUMMOBTARGETS":
                        powerEntry.maxMobTargets = Integer.parseInt(lineValues[1].trim());
                        break;
                    case "MAXNUMPLAYERTARGETS":
                        powerEntry.maxPlayerTargets = Integer.parseInt(lineValues[1].trim());
                        break;
                    case "ISADMINPOWER":
                        powerEntry.isAdminPower = Boolean.parseBoolean(lineValues[1].trim());
                        break;
                    case "ISPROJECTILE":
                        powerEntry.isProjectile = Boolean.parseBoolean(lineValues[1].trim());
                        break;
                    case "CASTERSPULSEPARTICLE":
                        powerEntry.casterPulseParticle = Integer.parseInt(lineValues[1].trim());
                        break;
                    case "TARGETEFFECTPREREQS_ORED":
                        EffectDescription preReq = new EffectDescription();
                        arguments = lineValues[1].trim().split("\\s+");
                        preReq.effect_id = arguments[0];
                        preReq.level = Integer.parseInt(arguments[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.toString().isEmpty() == false) {
                String[] conditions = conditionString.toString().split("\n");

                for (String condition : conditions) {
                    String[] parameters = condition.trim().split("\\s+");
                    powerEntry.conditions.put(parameters[0], Float.parseFloat(parameters[1]));
                }
            }

        } catch (Exception e) {
            Logger.error(powerEntry.power_id + " " + e);
        }

        return powerEntry;
    }

}

