/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2023  SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar.bausystem.features.simulator.storage;

import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.data.SimulatorPhase;
import de.steamwar.bausystem.features.simulator.data.observer.ObserverElement;
import de.steamwar.bausystem.features.simulator.data.observer.ObserverPhase;
import de.steamwar.bausystem.features.simulator.data.redstone.RedstoneElement;
import de.steamwar.bausystem.features.simulator.data.redstone.RedstonePhase;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
import org.bukkit.Material;
import org.bukkit.util.Vector;
import yapion.exceptions.YAPIONException;
import yapion.hierarchy.types.YAPIONArray;
import yapion.hierarchy.types.YAPIONObject;
import yapion.parser.YAPIONParser;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;

public class SimFormatSimulatorLoader implements SimulatorLoader {

    @Override
    public Optional<List<Simulator>> load(File file) {
        if (!file.getName().endsWith(".sim")) {
            return Optional.empty();
        }

        YAPIONObject yapionObject;
        try {
            yapionObject = YAPIONParser.parse(file);
        } catch (YAPIONException | IOException e) {
            return Optional.empty();
        }

        String name = file.getName().substring(0, file.getName().length() - ".sim".length());

        Simulator simulator = new Simulator(name);
        loadSimulator(yapionObject, simulator);
        return Optional.of(Collections.singletonList(simulator));
    }

    private void loadSimulator(YAPIONObject simulatorObject, Simulator simulator) {
        simulator.setMaterial(Material.valueOf(simulatorObject.getPlainValue("material")));
        simulator.setAutoTrace(simulatorObject.getPlainValue("autoTrace"));

        YAPIONArray groups = simulatorObject.getArray("groups");
        groups.streamObject().forEach(groupObject -> {
            SimulatorGroup simulatorGroup = new SimulatorGroup();
            loadGroup(groupObject, simulatorGroup);
            simulator.add(simulatorGroup);
        });
    }

    private void loadGroup(YAPIONObject groupObject, SimulatorGroup group) {
        group.setMaterial(Material.valueOf(groupObject.getPlainValue("material")));
        group.setDisabled(groupObject.getPlainValue("disabled"));

        YAPIONArray elements = groupObject.getArray("elements");
        elements.streamObject().forEach(elementObject -> {
            SimulatorElement<?> element;
            Supplier<SimulatorPhase> phaseConstructor;
            Vector position = new Vector(elementObject.getDouble("x"), elementObject.getDouble("y"), elementObject.getDouble("z"));
            String type = elementObject.getPlainValue("type");
            switch (type) {
                case "TNT":
                    element = new TNTElement(position);
                    phaseConstructor = TNTPhase::new;
                    break;
                case "Redstone":
                    element = new RedstoneElement(position);
                    phaseConstructor = RedstonePhase::new;
                    break;
                case "Observer":
                    element = new ObserverElement(position);
                    phaseConstructor = ObserverPhase::new;
                    break;
                default:
                    element = null;
                    phaseConstructor = null;
                    break;
            }
            if (element == null) {
                return;
            }
            loadElement(elementObject, element, phaseConstructor);
            group.add(element);
        });
    }

    private void loadElement(YAPIONObject elementObject, SimulatorElement<?> element, Supplier<SimulatorPhase> phaseConstructor) {
        element.setMaterial(Material.valueOf(elementObject.getPlainValue("material")));
        element.setDisabled(elementObject.getPlainValue("disabled"));
        element.loadExtra(elementObject);

        YAPIONArray phases = elementObject.getArray("phases");
        phases.streamObject().forEach(phaseObject -> {
            SimulatorPhase phase = phaseConstructor.get();
            loadPhase(phaseObject, phase);
            ((SimulatorElement) element).add(phase);
        });
    }

    private void loadPhase(YAPIONObject phaseObject, SimulatorPhase phase) {
        phase.setTickOffset(phaseObject.getPlainValue("tickOffset"));
        phase.setLifetime(phaseObject.getPlainValue("lifetime"));
        phase.setOrder(phaseObject.getPlainValue("order"));
        phase.loadExtra(phaseObject);
    }
}
