// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.core.modules.time;

import com.hypixel.hytale.math.vector.Vector3f;
import com.hypixel.hytale.protocol.InstantData;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import java.time.temporal.TemporalUnit;
import java.time.temporal.ChronoUnit;
import com.hypixel.hytale.math.util.TrigMathUtil;
import java.time.ZoneId;
import com.hypixel.hytale.server.core.asset.type.gameplay.WorldConfig;
import com.hypixel.hytale.server.core.universe.world.events.ecs.MoonPhaseChangeEvent;
import com.hypixel.hytale.math.util.MathUtil;
import java.time.temporal.TemporalField;
import java.time.temporal.ChronoField;
import com.hypixel.hytale.protocol.Packet;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.core.universe.world.PlayerUtil;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.component.ResourceType;
import com.hypixel.hytale.protocol.packets.world.UpdateTimeSettings;
import java.time.LocalDateTime;
import javax.annotation.Nonnull;
import com.hypixel.hytale.protocol.packets.world.UpdateTime;
import java.time.ZoneOffset;
import java.time.Instant;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Resource;

public class WorldTimeResource implements Resource<EntityStore>
{
    public static final long NANOS_PER_DAY;
    public static final int SECONDS_PER_DAY;
    public static final int HOURS_PER_DAY;
    public static final int DAYS_PER_YEAR;
    public static final Instant ZERO_YEAR;
    public static final Instant MAX_TIME;
    public static final ZoneOffset ZONE_OFFSET;
    public static final float SUN_HEIGHT = 2.0f;
    public static final boolean USE_SHADOW_MAPPING_SAFE_ANGLE = true;
    public static final float DAYTIME_PORTION_PERCENTAGE = 0.6f;
    public static final int DAYTIME_SECONDS;
    public static final int NIGHTTIME_SECONDS;
    public static final int SUNRISE_SECONDS;
    public static final float SHADOW_MAPPING_SAFE_ANGLE_LERP = 0.35f;
    @Nonnull
    private final UpdateTime currentTimePacket;
    private Instant gameTime;
    private LocalDateTime _gameTimeLocalDateTime;
    private int currentHour;
    private double sunlightFactor;
    private double scaledTime;
    private int moonPhase;
    @Nonnull
    private final UpdateTimeSettings currentSettings;
    @Nonnull
    private final UpdateTimeSettings tempSettings;
    
    public WorldTimeResource() {
        this.currentTimePacket = new UpdateTime();
        this.currentSettings = new UpdateTimeSettings();
        this.tempSettings = new UpdateTimeSettings();
    }
    
    @Nonnull
    public static ResourceType<EntityStore, WorldTimeResource> getResourceType() {
        return TimeModule.get().getWorldTimeResourceType();
    }
    
    public static double getSecondsPerTick(final World world) {
        final int daytimeDurationSeconds = world.getDaytimeDurationSeconds();
        final int nighttimeDurationSeconds = world.getNighttimeDurationSeconds();
        final int totalDurationSeconds = daytimeDurationSeconds + nighttimeDurationSeconds;
        return WorldTimeResource.SECONDS_PER_DAY / (double)totalDurationSeconds;
    }
    
    public void tick(final float dt, @Nonnull final Store<EntityStore> store) {
        final World world = store.getExternalData().getWorld();
        if (!updateTimeSettingsPacket(this.tempSettings, world).equals(this.currentSettings)) {
            final boolean wasTimePausedChanged = this.currentSettings.timePaused != this.tempSettings.timePaused;
            updateTimeSettingsPacket(this.currentSettings, world);
            PlayerUtil.broadcastPacketToPlayers(store, this.currentSettings);
            if (wasTimePausedChanged) {
                this.broadcastTimePacket(store);
            }
        }
        if (world.getWorldConfig().isGameTimePaused()) {
            return;
        }
        final int secondsOfDay = this._gameTimeLocalDateTime.get(ChronoField.SECOND_OF_DAY);
        final int daytimeDurationSeconds = world.getDaytimeDurationSeconds();
        final int nighttimeDurationSeconds = world.getNighttimeDurationSeconds();
        final int totalDurationSeconds = daytimeDurationSeconds + nighttimeDurationSeconds;
        final double daytimeRate = WorldTimeResource.DAYTIME_SECONDS / (double)daytimeDurationSeconds;
        final double nighttimeRate = WorldTimeResource.NIGHTTIME_SECONDS / (double)nighttimeDurationSeconds;
        double x0;
        if (secondsOfDay >= WorldTimeResource.SUNRISE_SECONDS && secondsOfDay < WorldTimeResource.SUNRISE_SECONDS + WorldTimeResource.DAYTIME_SECONDS) {
            x0 = (secondsOfDay - WorldTimeResource.SUNRISE_SECONDS) / daytimeRate;
        }
        else {
            x0 = daytimeDurationSeconds + MathUtil.floorMod(secondsOfDay - WorldTimeResource.SUNRISE_SECONDS - WorldTimeResource.DAYTIME_SECONDS, WorldTimeResource.SECONDS_PER_DAY) / nighttimeRate;
        }
        final double x2 = x0 + dt;
        final long whole = (long)Math.floor(x2 / totalDurationSeconds) - (long)Math.floor(x0 / totalDurationSeconds);
        final double m0 = MathUtil.floorMod(x0, totalDurationSeconds);
        final double m2 = MathUtil.floorMod(x2, totalDurationSeconds);
        final double f0 = (m0 <= daytimeDurationSeconds) ? (daytimeRate * m0) : (WorldTimeResource.DAYTIME_SECONDS + nighttimeRate * (m0 - daytimeDurationSeconds));
        final double f2 = (m2 <= daytimeDurationSeconds) ? (daytimeRate * m2) : (WorldTimeResource.DAYTIME_SECONDS + nighttimeRate * (m2 - daytimeDurationSeconds));
        final double advance = whole * WorldTimeResource.SECONDS_PER_DAY + (f2 - f0);
        Instant temp = this.gameTime.plusNanos((long)(advance * 1.0E9));
        if (temp.isBefore(WorldTimeResource.ZERO_YEAR)) {
            temp = WorldTimeResource.MAX_TIME.minusSeconds(WorldTimeResource.ZERO_YEAR.getEpochSecond() - this.gameTime.getEpochSecond()).minusNanos(WorldTimeResource.ZERO_YEAR.getNano() - this.gameTime.getNano());
        }
        if (temp.isAfter(WorldTimeResource.MAX_TIME)) {
            temp = WorldTimeResource.ZERO_YEAR.plusSeconds(WorldTimeResource.MAX_TIME.getEpochSecond() - this.gameTime.getEpochSecond()).plusNanos(WorldTimeResource.MAX_TIME.getNano() - this.gameTime.getNano());
        }
        this.setGameTime0(temp);
        this.updateMoonPhase(world, store);
    }
    
    public int getMoonPhase() {
        return this.moonPhase;
    }
    
    public void setMoonPhase(final int moonPhase, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        if (moonPhase != this.moonPhase) {
            final MoonPhaseChangeEvent event = new MoonPhaseChangeEvent(moonPhase);
            componentAccessor.invoke(event);
        }
        this.moonPhase = moonPhase;
    }
    
    public void updateMoonPhase(@Nonnull final World world, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final WorldConfig worldGameplayConfig = world.getGameplayConfig().getWorldConfig();
        final int totalMoonPhases = worldGameplayConfig.getTotalMoonPhases();
        final double dayProgress = this.currentHour / (double)WorldTimeResource.HOURS_PER_DAY;
        final int currentDay = this._gameTimeLocalDateTime.getDayOfYear();
        final int weekDay = (currentDay - 1) % totalMoonPhases;
        if (dayProgress < 0.5) {
            if (weekDay == 0) {
                this.setMoonPhase(totalMoonPhases - 1, componentAccessor);
            }
            else {
                this.setMoonPhase(weekDay - 1, componentAccessor);
            }
        }
        else {
            this.setMoonPhase(weekDay, componentAccessor);
        }
    }
    
    public boolean isMoonPhaseWithinRange(@Nonnull final World world, final int minMoonPhase, final int maxMoonPhase) {
        final WorldConfig worldGameplayConfig = world.getGameplayConfig().getWorldConfig();
        final int totalMoonPhases = worldGameplayConfig.getTotalMoonPhases();
        if (minMoonPhase > maxMoonPhase) {
            return MathUtil.within(this.moonPhase, minMoonPhase, totalMoonPhases) || MathUtil.within(this.moonPhase, 0.0, maxMoonPhase);
        }
        return MathUtil.within(this.moonPhase, minMoonPhase, maxMoonPhase);
    }
    
    public void setGameTime0(@Nonnull final Instant gameTime) {
        this.gameTime = gameTime;
        this._gameTimeLocalDateTime = LocalDateTime.ofInstant(gameTime, WorldTimeResource.ZONE_OFFSET);
        this.updateTimePacket(this.currentTimePacket);
        this.currentHour = this._gameTimeLocalDateTime.getHour();
        final int dayProgress = this._gameTimeLocalDateTime.get(ChronoField.SECOND_OF_DAY);
        final float dayDuration = 0.6f * WorldTimeResource.SECONDS_PER_DAY;
        final float nightDuration = WorldTimeResource.SECONDS_PER_DAY - dayDuration;
        final float halfNight = nightDuration * 0.5f;
        this.updateSunlightFactor(dayProgress, halfNight);
        this.updateScaledTime((float)dayProgress, dayDuration, halfNight);
    }
    
    private void updateSunlightFactor(final int dayProgress, final float halfNight) {
        final float dawnRelativeProgress = (dayProgress - halfNight) / WorldTimeResource.SECONDS_PER_DAY;
        this.sunlightFactor = MathUtil.clamp(TrigMathUtil.sin(6.2831855f * dawnRelativeProgress) + 0.2, 0.0, 1.0);
    }
    
    private void updateScaledTime(float dayProgress, final float dayDuration, final float halfNight) {
        if (dayProgress <= halfNight) {
            this.scaledTime = MathUtil.lerp(0.0f, 0.25f, dayProgress / halfNight);
            return;
        }
        dayProgress -= halfNight;
        if (dayProgress <= dayDuration) {
            this.scaledTime = MathUtil.lerp(0.25f, 0.75f, dayProgress / dayDuration);
            return;
        }
        dayProgress -= dayDuration;
        this.scaledTime = MathUtil.lerp(0.75f, 1.0f, dayProgress / halfNight);
    }
    
    public Instant getGameTime() {
        return this.gameTime;
    }
    
    public LocalDateTime getGameDateTime() {
        return this._gameTimeLocalDateTime;
    }
    
    public double getSunlightFactor() {
        return this.sunlightFactor;
    }
    
    public void setGameTime(@Nonnull final Instant gameTime, @Nonnull final World world, @Nonnull final Store<EntityStore> store) {
        this.setGameTime0(gameTime);
        this.updateMoonPhase(world, store);
        this.broadcastTimePacket(store);
    }
    
    public void setDayTime(final double dayTime, @Nonnull final World world, @Nonnull final Store<EntityStore> store) {
        if (dayTime < 0.0 || dayTime > 1.0) {
            throw new IllegalArgumentException("Day time must be between 0 and 1");
        }
        final Instant oldGameTime = this.gameTime;
        final Instant dayStart = oldGameTime.truncatedTo(ChronoUnit.DAYS);
        final Instant newGameTime = dayStart.plusNanos((long)(dayTime * WorldTimeResource.NANOS_PER_DAY));
        if (newGameTime.isBefore(oldGameTime)) {
            this.setGameTime(newGameTime.plus(1L, (TemporalUnit)ChronoUnit.DAYS), world, store);
        }
        else {
            this.setGameTime(newGameTime, world, store);
        }
    }
    
    public void broadcastTimePacket(@Nonnull final Store<EntityStore> store) {
        PlayerUtil.broadcastPacketToPlayers(store, this.currentTimePacket);
    }
    
    public void sendTimePackets(@Nonnull final PlayerRef playerRef) {
        playerRef.getPacketHandler().write(this.currentSettings);
        playerRef.getPacketHandler().write(this.currentTimePacket);
    }
    
    public boolean isDayTimeWithinRange(final double minTime, final double maxTime) {
        final double dayProgress = this._gameTimeLocalDateTime.getHour() / (double)WorldTimeResource.HOURS_PER_DAY;
        if (minTime > maxTime) {
            return MathUtil.within(dayProgress, minTime, 1.0) || MathUtil.within(dayProgress, 0.0, maxTime);
        }
        return MathUtil.within(dayProgress, minTime, maxTime);
    }
    
    public void updateTimePacket(@Nonnull final UpdateTime currentTimePacket) {
        if (currentTimePacket.gameTime == null) {
            currentTimePacket.gameTime = new InstantData();
        }
        currentTimePacket.gameTime.seconds = this.gameTime.getEpochSecond();
        currentTimePacket.gameTime.nanos = this.gameTime.getNano();
    }
    
    @Nonnull
    public static UpdateTimeSettings updateTimeSettingsPacket(@Nonnull final UpdateTimeSettings settings, @Nonnull final World world) {
        final WorldConfig worldGameplayConfig = world.getGameplayConfig().getWorldConfig();
        settings.daytimeDurationSeconds = world.getDaytimeDurationSeconds();
        settings.nighttimeDurationSeconds = world.getNighttimeDurationSeconds();
        settings.totalMoonPhases = (byte)worldGameplayConfig.getTotalMoonPhases();
        settings.timePaused = world.getWorldConfig().isGameTimePaused();
        return settings;
    }
    
    public boolean isScaledDayTimeWithinRange(final double minTime, final double maxTime) {
        if (minTime > maxTime) {
            return MathUtil.within(this.scaledTime, minTime, 1.0) || MathUtil.within(this.scaledTime, 0.0, maxTime);
        }
        return MathUtil.within(this.scaledTime, minTime, maxTime);
    }
    
    public boolean isYearWithinRange(final double minTime, final double maxTime) {
        return false;
    }
    
    public int getCurrentHour() {
        return this.currentHour;
    }
    
    public float getDayProgress() {
        return this._gameTimeLocalDateTime.get(ChronoField.SECOND_OF_DAY) / (float)WorldTimeResource.SECONDS_PER_DAY;
    }
    
    @Nonnull
    public Vector3f getSunDirection() {
        final float dayTime = this.getDayProgress() * WorldTimeResource.HOURS_PER_DAY;
        final float daylightDuration = 0.6f * WorldTimeResource.HOURS_PER_DAY;
        final float nightDuration = WorldTimeResource.HOURS_PER_DAY - daylightDuration;
        final float halfNightDuration = nightDuration * 0.5f;
        float sunAngle;
        if (dayTime < halfNightDuration) {
            final float inverseAllNightDay = 1.0f / (nightDuration * 2.0f);
            sunAngle = MathUtil.wrapAngle((dayTime * inverseAllNightDay - halfNightDuration * inverseAllNightDay) * 6.2831855f);
        }
        else if (dayTime > WorldTimeResource.HOURS_PER_DAY - halfNightDuration) {
            final float inverseAllNightDay = 1.0f / (nightDuration * 2.0f);
            sunAngle = MathUtil.wrapAngle((dayTime * inverseAllNightDay - (WorldTimeResource.HOURS_PER_DAY + halfNightDuration) * inverseAllNightDay) * 6.2831855f);
        }
        else {
            final float halfDaylightDuration = daylightDuration * 0.5f;
            final float inverseAllDaylightDay = 1.0f / (daylightDuration * 2.0f);
            sunAngle = MathUtil.wrapAngle((dayTime * inverseAllDaylightDay - (WorldTimeResource.HOURS_PER_DAY * 0.5f - halfDaylightDuration) * inverseAllDaylightDay) * 6.2831855f);
        }
        final Vector3f sunPosition = new Vector3f(TrigMathUtil.cos(sunAngle), TrigMathUtil.sin(sunAngle) * 2.0f, TrigMathUtil.sin(sunAngle));
        sunPosition.normalize();
        final float tweakedSunHeight = sunPosition.y + 0.2f;
        if (tweakedSunHeight > 0.0f) {
            sunPosition.scale(-1.0f);
        }
        sunPosition.x = MathUtil.lerp(sunPosition.x, Vector3f.DOWN.x, 0.35f);
        sunPosition.y = MathUtil.lerp(sunPosition.y, Vector3f.DOWN.y, 0.35f);
        sunPosition.z = MathUtil.lerp(sunPosition.z, Vector3f.DOWN.z, 0.35f);
        return sunPosition;
    }
    
    @Nonnull
    public static InstantData instantToInstantData(@Nonnull final Instant instant) {
        return new InstantData(instant.getEpochSecond(), instant.getNano());
    }
    
    @Nonnull
    public static Instant instantDataToInstant(@Nonnull final InstantData instantData) {
        return Instant.ofEpochSecond(instantData.seconds, instantData.nanos);
    }
    
    @Nonnull
    @Override
    public Resource<EntityStore> clone() {
        final WorldTimeResource worldTimeComponent = new WorldTimeResource();
        worldTimeComponent.gameTime = this.gameTime;
        worldTimeComponent._gameTimeLocalDateTime = this._gameTimeLocalDateTime;
        worldTimeComponent.currentHour = this.currentHour;
        worldTimeComponent.sunlightFactor = this.sunlightFactor;
        worldTimeComponent.scaledTime = this.scaledTime;
        return worldTimeComponent;
    }
    
    @Nonnull
    @Override
    public String toString() {
        return "WorldTimeResource{, gameTime=" + String.valueOf(this.gameTime);
    }
    
    static {
        NANOS_PER_DAY = ChronoUnit.DAYS.getDuration().toNanos();
        SECONDS_PER_DAY = (int)ChronoUnit.DAYS.getDuration().getSeconds();
        HOURS_PER_DAY = (int)ChronoUnit.DAYS.getDuration().toHours();
        DAYS_PER_YEAR = (int)ChronoUnit.YEARS.getDuration().toDays();
        ZERO_YEAR = Instant.parse("0001-01-01T00:00:00.00Z");
        MAX_TIME = Instant.ofEpochSecond(31553789759L, 99999999L);
        ZONE_OFFSET = ZoneOffset.UTC;
        DAYTIME_SECONDS = (int)(WorldTimeResource.SECONDS_PER_DAY * 0.6f);
        NIGHTTIME_SECONDS = (int)(WorldTimeResource.SECONDS_PER_DAY * 0.39999998f);
        SUNRISE_SECONDS = WorldTimeResource.NIGHTTIME_SECONDS / 2;
    }
}
