/*
 * SoulFire
 * Copyright (C) 2026  AlexProgrammerDE
 *
 * 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 com.soulfiremc.server.util;

import org.checkerframework.checker.nullness.qual.Nullable;

import java.time.Duration;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;

/// Simple class to make waiting easier and less verbose.
public final class TimeUtil {
  public static final long NANOSECONDS_PER_SECOND = TimeUnit.SECONDS.toNanos(1L);
  public static final long NANOSECONDS_PER_MILLISECOND = TimeUnit.MILLISECONDS.toNanos(1L);

  private TimeUtil() {}

  public static void waitTime(long time, TimeUnit unit) {
    if (time <= 0) {
      return;
    }

    try {
      ForkJoinPool.managedBlock(new ForkJoinPool.ManagedBlocker() {
        private final long endTime = System.nanoTime() + unit.toNanos(time);

        public boolean isReleasable() {
          return System.nanoTime() >= endTime;
        }

        public boolean block() throws InterruptedException {
          var remainingTime = endTime - System.nanoTime();
          if (remainingTime > 0) {
            Thread.sleep(remainingTime / NANOSECONDS_PER_MILLISECOND, (int) (remainingTime % NANOSECONDS_PER_MILLISECOND));
          }

          return true;
        }
      });
    } catch (InterruptedException _) {
      Thread.currentThread().interrupt();
    }
  }

  public static void waitCondition(BooleanSupplier condition) {
    waitCondition(condition, null);
  }

  public static void waitCondition(BooleanSupplier condition, @Nullable Duration timeout) {
    var startTime = System.nanoTime();
    try {
      ForkJoinPool.managedBlock(new ForkJoinPool.ManagedBlocker() {
        public boolean isReleasable() {
          return !condition.getAsBoolean() || (timeout != null && System.nanoTime() - startTime >= timeout.toNanos());
        }

        public boolean block() throws InterruptedException {
          Thread.sleep(1);
          return !condition.getAsBoolean();
        }
      });
    } catch (InterruptedException _) {
      Thread.currentThread().interrupt();
    }
  }
}
