package emu.nebula.game;

import java.time.Instant;
import java.time.LocalDate;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import emu.nebula.GameConstants;
import emu.nebula.Nebula;
import emu.nebula.game.gacha.GachaModule;
import emu.nebula.game.player.PlayerModule;
import emu.nebula.game.scoreboss.ScoreBossModule;
import emu.nebula.game.tutorial.TutorialModule;
import emu.nebula.net.GameSession;

import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;

import lombok.Getter;

@Getter
public class GameContext implements Runnable {
    private final Object2ObjectMap<String, GameSession> sessions;
    
    // Modules
    private final PlayerModule playerModule;
    private final GachaModule gachaModule;
    private final TutorialModule tutorialModule;
    private final ScoreBossModule scoreBossModule;
    
    // Game loop
    private final ScheduledExecutorService scheduler;
    
    // Daily
    private long epochDays;
    
    public GameContext() {
        this.sessions = new Object2ObjectOpenHashMap<>();
        
        // Setup game modules
        this.playerModule = new PlayerModule(this);
        this.gachaModule = new GachaModule(this);
        this.tutorialModule = new TutorialModule(this);
        this.scoreBossModule = new ScoreBossModule(this);
        
        // Run game loop
        this.scheduler = Executors.newScheduledThreadPool(1);
        this.scheduler.scheduleAtFixedRate(this, 0, 1, TimeUnit.SECONDS);
    }
    
    public synchronized GameSession getSessionByToken(String token) {
        return sessions.get(token);
    }
    
    public synchronized void addSession(GameSession session) {
        this.sessions.put(session.getToken(), session);
    }

    public synchronized void generateSessionToken(GameSession session) {
        // Remove token
        if (session.getToken() != null) {
            this.sessions.remove(session.getToken());
        }
        
        // Generate token
        String token = null;
        
        do {
            token = session.generateToken();
        } while (this.getSessions().containsKey(token));
        
        // Register session
        this.sessions.put(session.getToken(), session);
    }
    
    // TODO add timeout to config
    public synchronized void cleanupInactiveSessions() {
        var it = this.getSessions().entrySet().iterator();
        long timeout = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(600); // 10 minutes
        
        while (it.hasNext()) {
            var session = it.next().getValue();
            
            if (timeout > session.getLastActiveTime() || session.isRemove()) {
                // Remove from session map
                it.remove();
                
                // Clear player
                session.clearPlayer();
            }
        }
    }

    @Override
    public void run() {
        // Check daily - Update epoch days
        long offset = Nebula.getConfig().getServerOptions().getDailyResetHour() * -3600;
        var instant = Instant.now().plusSeconds(offset);
        var date = LocalDate.ofInstant(instant, GameConstants.UTC_ZONE);
        
        long lastEpochDays = this.epochDays;
        this.epochDays = date.toEpochDay();
        
        // Check if the day was changed
        if (this.epochDays > lastEpochDays) {
            this.resetDailies();
        }
        
        // Clean up any inactive sessions
        this.cleanupInactiveSessions();
    }

    /**
     * Resets the daily missions/etc for all players on the server
     */
    public synchronized void resetDailies() {
        for (var session : this.getSessions().values()) {
            // Cache
            var player = session.getPlayer();
            
            // Skip if session doesn't have a player
            if (player == null) {
                continue;
            }
            
            //
            player.checkResetDailies();
        }
    }
}
