package mindustry.service;

import arc.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.entities.bullet.*;
import mindustry.game.EventType.*;
import mindustry.game.SectorInfo.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.defense.Wall.*;
import mindustry.world.blocks.defense.turrets.Turret.*;
import mindustry.world.blocks.distribution.*;
import mindustry.world.blocks.production.AttributeCrafter.*;
import mindustry.world.blocks.production.SolidPump.*;
import mindustry.world.blocks.storage.*;
import mindustry.world.meta.*;

import static mindustry.Vars.*;
import static mindustry.service.Achievement.*;

/**
 * Interface for handling game service across multiple platforms.
 *
 * This includes:
 * - Desktop (Steam)
 *
 * The default implementation does nothing.
 * */
public class GameService{
    private Seq<Tile> tmpTiles = new Seq<>();
    private ObjectSet<String> blocksBuilt = new ObjectSet<>(), unitsBuilt = new ObjectSet<>();
    private ObjectSet<UnitType> t5s = new ObjectSet<>();
    private IntSet checked = new IntSet();

    private Block[] allTransportSerpulo, allTransportErekir, allErekirBlocks, allSerpuloBlocks;

    /** Begin listening for new achievement events, once the game service is activated. This can be called at any time, but only once. */
    public void init(){
        if(clientLoaded){
            registerEvents();
        }else{
            Events.on(ClientLoadEvent.class, e -> registerEvents());
        }
    }

    public boolean enabled(){
        return false;
    }

    public void completeAchievement(String name){

    }

    public void clearAchievement(String name){

    }

    public boolean isAchieved(String name){
        return false;
    }

    public int getStat(String name, int def){
        return def;
    }

    public void setStat(String name, int amount){

    }

    public void storeStats(){

    }

    private void checkAllBlocks(Achievement ach, Block[] blocks){
        if(!Structs.contains(blocks, t -> !blocksBuilt.contains(t.name))){
            ach.complete();
        }
    }

    private void registerEvents(){
        allTransportSerpulo = content.blocks().select(b -> b.category == Category.distribution && b.isOnPlanet(Planets.serpulo) && b.isVanilla() && b.buildVisibility == BuildVisibility.shown).toArray(Block.class);
        allTransportErekir = content.blocks().select(b -> b.category == Category.distribution && b.isOnPlanet(Planets.erekir) && b.isVanilla() && b.buildVisibility == BuildVisibility.shown).toArray(Block.class);

        //cores are ignored since they're upgrades and can be skipped
        allSerpuloBlocks = content.blocks().select(b -> b.synthetic() && b.isOnPlanet(Planets.serpulo) && b.isVanilla() && !(b instanceof CoreBlock) && b.buildVisibility == BuildVisibility.shown).toArray(Block.class);
        allErekirBlocks = content.blocks().select(b -> b.synthetic() && b.isOnPlanet(Planets.erekir) && b.isVanilla() && !(b instanceof CoreBlock) && b.buildVisibility == BuildVisibility.shown).toArray(Block.class);

        unitsBuilt = Core.settings.getJson("units-built" , ObjectSet.class, String.class, ObjectSet::new);
        blocksBuilt = Core.settings.getJson("blocks-built" , ObjectSet.class, String.class, ObjectSet::new);
        t5s = ObjectSet.with(UnitTypes.omura, UnitTypes.reign, UnitTypes.toxopid, UnitTypes.eclipse, UnitTypes.oct, UnitTypes.corvus);

        checkAllBlocks(allBlocksErekir, allErekirBlocks);
        checkAllBlocks(allBlocksSerpulo, allSerpuloBlocks);

        //periodically check for various conditions
        float updateInterval = 2f;
        Timer.schedule(this::checkUpdate, updateInterval, updateInterval);

        if(Items.thorium.unlocked()) obtainThorium.complete();
        if(Items.titanium.unlocked()) obtainTitanium.complete();

        if(SectorPresets.origin.sector.isCaptured()){
            completeErekir.complete();
        }

        if(SectorPresets.planetaryTerminal.sector.isCaptured()){
            completeSerpulo.complete();
        }

        if(mods != null && mods.list().size > 0){
            installMod.complete();
        }

        if(Core.bundle.get("ok").equals(Blocks.router.emoji()+Blocks.router.emoji())){
            routerLanguage.complete();
        }

        if(!Planets.serpulo.sectors.contains(s -> !s.isCaptured())){
            captureAllSectors.complete();
        }

        Events.run(Trigger.openConsole, () -> openConsole.complete());

        Events.run(Trigger.unitCommandAttack, () -> {
            if(campaign()){
                issueAttackCommand.complete();
            }
        });

        Events.on(UnitDestroyEvent.class, e -> {
            if(campaign()){
                if(e.unit.team != Vars.player.team()){
                    SStat.unitsDestroyed.add();

                    if(e.unit.isBoss()){
                        SStat.bossesDefeated.add();
                    }
                }
            }
        });

        Events.on(TurnEvent.class, e -> {
            float total = 0;
            for(Planet planet : content.planets()){
                for(Sector sec : planet.sectors){
                    if(sec.hasBase()){
                        for(ExportStat v : sec.info.production.values()){
                            if(v.mean > 0) total += v.mean * 60;
                        }
                    }
                }
            }

            SStat.maxProduction.max(Math.round(total));
        });

        Events.run(Trigger.update, () -> {
            //extremely lazy timer, I just don't care
            if(campaign() && !hoverUnitLiquid.isAchieved() && Core.graphics.getFrameId() % 20 == 0){
                var units = state.rules.defaultTeam.data().getUnits(UnitTypes.elude);
                if(units != null){
                    for(var unit : units){
                        if(unit.floorOn().isLiquid){
                            hoverUnitLiquid.complete();
                            break;
                        }
                    }
                }
            }

            if(campaign() && !player.dead() && player.unit().type.canBoost && player.unit().elevation >= 0.25f){
                boostUnit.complete();
            }
        });

        Events.run(Trigger.unitCommandBoost, () -> {
           if(campaign()){
                boostUnit.complete();
            }
        });

        Events.run(Trigger.newGame, () -> Core.app.post(() -> {
            if(campaign() && player.core() != null && player.core().items.total() >= 10 * 1000){
                drop10kitems.complete();
            }
        }));

        Events.on(BuildingBulletDestroyEvent.class, e -> {
            if(campaign() && e.build.block == Blocks.scatter && e.build.team == state.rules.waveTeam && e.bullet.owner instanceof Unit u && u.type == UnitTypes.flare && u.team == player.team()){
                destroyScatterFlare.complete();
            }
        });

        Events.on(BlockBuildEndEvent.class, e -> {
            if(campaign() && state.rules.sector == SectorPresets.groundZero.sector && e.tile.block() == Blocks.coreNucleus){
                nucleusGroundZero.complete();
            }
        });

        Events.on(BlockBuildEndEvent.class, e -> {
            if(campaign() && e.unit != null && e.unit.team == state.rules.defaultTeam && !e.breaking){
                SStat.blocksBuilt.add();

                if(e.tile.block() == Blocks.router && e.tile.build.proximity.contains(t -> t.block == Blocks.router)){
                    chainRouters.complete();
                }

                if(e.tile.block() == Blocks.groundFactory){
                    buildGroundFactory.complete();
                }

                if((e.tile.build instanceof AttributeCrafterBuild a && a.attrsum > 0) || (e.tile.build instanceof SolidPumpBuild sp && sp.boost > 0)){
                    boostBuildingFloor.complete();
                }

                if(!allTransportOneMap.isAchieved()){
                    Block[] allTransports = state.rules.sector.planet == Planets.erekir ? allTransportErekir : allTransportSerpulo;
                    boolean all = true;
                    for(var block : allTransports){
                        if(state.rules.defaultTeam.data().getCount(block) == 0){
                            all = false;
                            break;
                        }
                    }
                    if(all){
                        allTransportOneMap.complete();
                    }
                }

                if(e.tile.block() == Blocks.mendProjector) buildMendProjector.complete();
                if(e.tile.block() == Blocks.overdriveProjector) buildOverdriveProjector.complete();

                if(e.tile.block() == Blocks.waterExtractor){
                    if(e.tile.getLinkedTiles(tmpTiles).contains(t -> t.floor().liquidDrop == Liquids.water)){
                        buildWexWater.complete();
                    }
                }

                if(blocksBuilt.add(e.tile.block().name)){
                    if(state.rules.sector.planet == Planets.erekir){
                        checkAllBlocks(allBlocksErekir, allErekirBlocks);
                    }else{
                        checkAllBlocks(allBlocksSerpulo, allSerpuloBlocks);
                    }

                    if(blocksBuilt.contains("meltdown") && blocksBuilt.contains("spectre") && blocksBuilt.contains("foreshadow")){
                        buildMeltdownSpectre.complete();
                    }

                    save();
                }

                if(!circleConveyor.isAchieved() && e.tile.block() instanceof Conveyor){
                    checked.clear();
                    check: {
                        Tile current = e.tile;
                        for(int i = 0; i < 4; i++){
                            checked.add(current.pos());
                            if(current.build == null) break check;
                            Tile next = current.nearby(current.build.rotation);
                            if(next != null && next.block() instanceof Conveyor){
                                current = next;
                            }else{
                                break check;
                            }
                        }

                        if(current == e.tile && checked.size == 4){
                            circleConveyor.complete();
                        }
                    }
                }
            }

            if(campaign() && e.unit != null && e.unit.isLocal() && e.breaking){
                //hacky way of testing for boulders without string contains/endsWith
                if(e.tile.block().breakSound == Sounds.rockBreak){
                    SStat.bouldersDeconstructed.add();
                }
            }
        });

        Events.on(TurnEvent.class, e -> {
            int total = 0;
            for(var planet : content.planets()){
                for(var sector : planet.sectors){
                    if(sector.hasBase()){
                        total += sector.items().total;
                    }
                }
            }

            SStat.totalCampaignItems.max(total);
        });

        Events.on(SectorLaunchLoadoutEvent.class, e -> {
            if(e.sector.planet == Planets.serpulo && !schematics.isDefaultLoadout(e.loadout)){
                launchCoreSchematic.complete();
            }
        });

        Events.on(UnitCreateEvent.class, e -> {
            if(campaign() && e.unit.team == state.rules.defaultTeam){
                if(unitsBuilt.add(e.unit.type.name)){
                    SStat.unitTypesBuilt.max(content.units().count(u -> unitsBuilt.contains(u.name) && !u.isHidden()));
                    save();
                }

                if(t5s.contains(e.unit.type)){
                    buildT5.complete();
                }
            }
        });

        Events.on(SaveLoadEvent.class, e -> Core.app.post(() -> Core.app.post(() -> {
            if(campaign()){
                boolean added = false;
                for(UnitType type : Vars.content.units()){
                    var all = state.rules.defaultTeam.data().getUnits(type);
                    if(all != null && all.size > 0){
                        if(t5s.contains(type)){
                            buildT5.complete();
                        }

                        if(unitsBuilt.add(type.name)){
                            added = true;
                        }
                    }
                }

                if(added){
                    SStat.unitTypesBuilt.max(content.units().count(u -> unitsBuilt.contains(u.name) && !u.isHidden()));
                    save();
                }
            }
        })));

        Events.on(UnitControlEvent.class, e -> {
            if(e.unit instanceof BlockUnitc unit && unit.tile().block == Blocks.router){
                becomeRouter.complete();
            }

            if(e.unit instanceof BlockUnitc unit && unit.tile() instanceof TurretBuild){
                controlTurret.complete();
            }
        });

        Events.on(SchematicCreateEvent.class, e -> {
            SStat.schematicsCreated.add();
        });

        Events.on(BlockDestroyEvent.class, e -> {
            if(campaign() && e.tile.team() != player.team()){
                SStat.blocksDestroyed.add();
            }
        });

        Events.on(MapMakeEvent.class, e -> SStat.mapsMade.add());

        Events.on(MapPublishEvent.class, e -> SStat.mapsPublished.add());

        Events.on(UnlockEvent.class, e -> {
            if(e.content == Items.thorium) obtainThorium.complete();
            if(e.content == Items.titanium) obtainTitanium.complete();
        });

        Events.run(Trigger.openWiki, openWiki::complete);

        Events.run(Trigger.importMod, installMod::complete);

        Events.run(Trigger.exclusionDeath, dieExclusion::complete);

        Events.on(UnitDrownEvent.class, e -> {
            if(campaign() && e.unit.isPlayer()){
                drown.complete();
            }
        });

        trigger(Trigger.impactPower, powerupImpactReactor);

        trigger(Trigger.flameAmmo, useFlameAmmo);

        trigger(Trigger.turretCool, coolTurret);

        trigger(Trigger.suicideBomb, suicideBomb);

        trigger(Trigger.blastGenerator, blastGenerator);

        trigger(Trigger.forceProjectorBreak, breakForceProjector);

        trigger(Trigger.neoplasmReact, neoplasmWater);

        trigger(Trigger.shockwaveTowerUse, shockwaveTowerUse);

        Events.run(Trigger.enablePixelation, enablePixelation::complete);

        Events.run(Trigger.thoriumReactorOverheat, () -> {
            if(campaign()){
                SStat.reactorsOverheated.add();
            }
        });

        Events.on(GeneratorPressureExplodeEvent.class, e -> {
            if(campaign() && e.build.block == Blocks.neoplasiaReactor){
                neoplasiaExplosion.complete();
            }
        });

        trigger(Trigger.shock, shockWetEnemy);

        trigger(Trigger.blastFreeze, blastFrozenUnit);

        Events.on(UnitBulletDestroyEvent.class, e -> {
            if(state.isCampaign() && player != null && player.team() == e.bullet.team){

                if(e.bullet.owner instanceof WallBuild){
                    killEnemyPhaseWall.complete();
                }

                if(e.unit.type == UnitTypes.eclipse && e.bullet.owner instanceof TurretBuild turret && turret.block == Blocks.duo){
                    killEclipseDuo.complete();
                }

                if(e.bullet.type instanceof MassDriverBolt){
                    killMassDriver.complete();
                }
            }
        });

        Events.on(LaunchItemEvent.class, e -> {
            if(campaign()){
                launchItemPad.complete();
            }
        });

        Events.on(PickupEvent.class, e -> {
            if(e.carrier.isPlayer() && campaign() && e.unit != null && t5s.contains(e.unit.type)){
                pickupT5.complete();
            }
        });

        Events.on(UnitCreateEvent.class, e -> {
            if(campaign() && e.unit.team() == player.team()){
                SStat.unitsBuilt.add();
            }
        });

        Events.on(SectorLaunchEvent.class, e -> {
            SStat.timesLaunched.add();
        });

        Events.on(LaunchItemEvent.class, e -> {
            SStat.itemsLaunched.add(e.stack.amount);
        });

        Events.on(WaveEvent.class, e -> {
            if(campaign()){
                SStat.maxWavesSurvived.max(Vars.state.wave);

                //TODO: remove (263 is uniquely broken)
                if(state.stats.buildingsBuilt == 0 && state.wave >= 10 && !(Vars.state.getSector() != null && Vars.state.getPlanet() == Planets.serpulo && Vars.state.getSector().id == 263)){
                    survive10WavesNoBlocks.complete();
                }
            }
        });

        Events.on(PlayerJoin.class, e -> {
            if(Vars.net.server()){
                SStat.maxPlayersServer.max(Groups.player.size());
            }
        });

        Runnable checkUnlocks = () -> {
            if(Blocks.router.unlocked()) researchRouter.complete();

            if(!TechTree.all.contains(t -> t.content.locked())){
                researchAll.complete();
            }

            if(Blocks.microProcessor.unlocked()) researchLogic.complete();
        };

        //check unlocked stuff on load as well
        Events.on(ResearchEvent.class, e -> checkUnlocks.run());
        Events.on(UnlockEvent.class, e -> checkUnlocks.run());

        checkUnlocks.run();

        Events.on(WinEvent.class, e -> {
            if(state.rules.pvp){
                SStat.pvpsWon.add();
            }
        });

        Events.on(ClientPreConnectEvent.class, e -> {
            if(e.host != null && !e.host.address.startsWith("steam:") && !e.host.address.startsWith("192.")){
                joinCommunityServer.complete();
            }
        });

        Events.on(SectorCaptureEvent.class, e -> {
            if(e.sector.isBeingPlayed() || net.client()){
                if(Vars.state.wave <= 5 && state.rules.attackMode){
                    defeatAttack5Waves.complete();
                }

                if(state.stats.buildingsDestroyed == 0){
                    captureNoBlocksBroken.complete();
                }
            }

            if(Vars.state.rules.attackMode){
                SStat.attacksWon.add();
            }

            if(!e.sector.isBeingPlayed() && !net.client()){
                captureBackground.complete();
            }

            if(e.sector.planet == Planets.serpulo && !e.sector.planet.sectors.contains(s -> !s.hasBase())){
                captureAllSectors.complete();
            }

            if(e.sector.planet == Planets.erekir && e.sector.preset != null && e.sector.preset.isLastSector){
                completeErekir.complete();
            }

            if(e.sector.planet == Planets.serpulo && e.sector.preset != null && e.sector.preset.isLastSector){
                completeSerpulo.complete();
            }

            //TODO wrong
            if(e.sector.planet == Planets.serpulo){
                SStat.sectorsControlled.set(e.sector.planet.sectors.count(Sector::hasBase));
            }
        });

        Events.on(PayloadDropEvent.class, e -> {
            if(campaign() && e.unit != null && e.carrier.team == state.rules.defaultTeam && state.rules.waveTeam.cores().contains(c -> c.within(e.unit, state.rules.enemyCoreBuildRadius))){
                dropUnitsCoreZone.complete();
            }
        });

        Events.on(ClientChatEvent.class, e -> {
            if(e.message.contains(Iconc.alphaaaa + "")){
                useAnimdustryEmoji.complete();
            }
        });
    }

    private void checkUpdate(){
        if(campaign()){
            SStat.maxUnitActive.max(Groups.unit.count(t -> t.team == player.team()));

            if(Groups.unit.count(u -> u.type == UnitTypes.poly && u.team == player.team()) >= 10){
                active10Polys.complete();
            }

            for(Building entity : player.team().cores()){
                if(!content.items().contains(i -> i.isOnPlanet(state.getPlanet()) && entity.items.get(i) < entity.block.itemCapacity)){
                    fillCoreAllCampaign.complete();
                    break;
                }
            }

            for(var up : Groups.powerGraph){
                var graph = up.graph();
                if(graph.all.size > 1 && graph.all.first().team == player.team() && graph.hasPowerBalanceSamples()){
                    float balance = graph.getPowerBalance() * 60f;

                    if(balance < -10_000) negative10kPower.complete();
                    if(balance > 100_000) positive100kPower.complete();
                    if(graph.getBatteryStored() > 1_000_000) store1milPower.complete();
                }
            }
        }
    }

    private void save(){
        Core.settings.putJson("units-built" , String.class, unitsBuilt);
        Core.settings.putJson("blocks-built" , String.class, blocksBuilt);
    }

    private void trigger(Trigger trigger, Achievement ach){
        Events.run(trigger, () -> {
            if(campaign()){
                ach.complete();
            }
        });
    }

    private boolean campaign(){
        return Vars.state.isCampaign();
    }
}
