//SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

import "./SimpleOracle.sol";
import { StatisticsUtils } from "../utils/StatisticsUtils.sol";

contract WhitelistOracle {
    using StatisticsUtils for uint256[];

    /////////////////
    /// Errors //////
    /////////////////

    error OnlyOwner();
    error IndexOutOfBounds();
    error NoOraclesAvailable();

    //////////////////////
    /// State Variables //
    //////////////////////

    address public owner;
    SimpleOracle[] public oracles;
    uint256 public constant STALE_DATA_WINDOW = 24 seconds;

    ////////////////
    /// Events /////
    ////////////////

    event OracleAdded(address oracleAddress, address oracleOwner);
    event OracleRemoved(address oracleAddress);

    ///////////////////
    /// Modifiers /////
    ///////////////////

    /**
     * @notice Modifier to restrict function access to the contract owner
     * @dev Currently disabled to make it easy for you to impersonate the owner
     */
    modifier onlyOwner() {
        // if (msg.sender != owner) revert OnlyOwner();
        _;
    }

    ///////////////////
    /// Constructor ///
    ///////////////////

    constructor() {
        owner = msg.sender;
    }

    ///////////////////
    /// Functions /////
    ///////////////////

    /**
     * @notice Adds a new oracle to the whitelist by deploying a SimpleOracle contract (only contract owner)
     * @dev Creates a new SimpleOracle instance and adds it to the oracles array.
     * @param _owner The address that will own the newly created oracle and can update its price
     */
    function addOracle(address _owner) public onlyOwner {
      SimpleOracle newOracle = new SimpleOracle(_owner);
      oracles.push(newOracle);
      emit OracleAdded(address(newOracle), _owner);
    }

    /**
     * @notice Removes an oracle from the whitelist by its array index (only contract owner)
     * @dev Uses swap-and-pop pattern for gas-efficient removal. Order is not preserved.
     *      Reverts with IndexOutOfBounds, if the provided index is >= oracles.length.
     * @param index The index of the oracle to remove from the oracles array
     */
    function removeOracle(uint256 index) public onlyOwner {
      uint256 oraclesSize = oracles.length;
      if (index >= oraclesSize) revert IndexOutOfBounds();

      address deletedOracle = address(oracles[index]);
      if (index != oraclesSize - 1) {
        oracles[index] = oracles[oraclesSize - 1];
      }
      
      oracles.pop();
      emit OracleRemoved(deletedOracle);
    }

    /**
     * @notice Returns the aggregated price from all active oracles using median calculation
     * @dev Filters oracles with timestamps older than STALE_DATA_WINDOW, then calculates median
     *      of remaining valid prices. Uses StatisticsUtils for sorting and median calculation.
     * @return The median price from all active oracles
     */
    function getPrice() public view returns (uint256) {
      if (oracles.length == 0) revert NoOraclesAvailable();
      uint256[] memory prices = new uint256[](oracles.length);
      uint256 priceIndex = 0;
      uint256 currentTime = block.timestamp;

      for (uint256 i = 0; i < oracles.length; i++) {
        (uint256 price, uint256 timestamp) = oracles[i].getPrice();
	if (currentTime - timestamp < STALE_DATA_WINDOW) {
	  prices[priceIndex] = price;
	  priceIndex++;
	}
      }

      uint256[] memory fixedPrices = new uint256[](priceIndex);
      for (uint256 i = 0; i < priceIndex; i++) {
        fixedPrices[i] = prices[i];
      }

      fixedPrices.sort();
      return fixedPrices.getMedian();
    }

    /**
     * @notice Returns the addresses of all oracles that have updated their price within the last STALE_DATA_WINDOW
     * @dev Iterates through all oracles and filters those with recent timestamps (within STALE_DATA_WINDOW).
     *      Uses a temporary array to collect active nodes, then creates a right-sized return array
     *      for gas optimization.
     * @return An array of addresses representing the currently active oracle contracts
     */
    function getActiveOracleNodes() public view returns (address[] memory) {
      address[] memory activeOracles = new address[](oracles.length);
      uint256 oracleIndex = 0;
      uint256 currentTime = block.timestamp;

      for (uint256 i = 0; i < oracles.length; i++) {
        (, uint256 timestamp) = oracles[i].getPrice();
	if (currentTime - timestamp < STALE_DATA_WINDOW) {
	  activeOracles[oracleIndex] = address(oracles[i]);
	  oracleIndex++;
	}
      }

      address[] memory fixedActiveOracles = new address[](oracleIndex);
      for (uint256 i = 0; i < oracleIndex; i++) {
        fixedActiveOracles[i] = activeOracles[i];
      }

      return fixedActiveOracles;
    }
}
