// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable2Step} from "@openzeppelin-contracts/access/Ownable2Step.sol";
import {Ownable} from "@openzeppelin-contracts/access/Ownable.sol";

import {ITreasury} from "../interfaces/ITreasury.sol";
import {IGHST} from "../interfaces/IGHST.sol";
import {IStaking} from "../interfaces/IStaking.sol";
import {IClaim} from "../interfaces/IClaim.sol";

contract GenesisClaim is IClaim, Ownable2Step {
    using SafeERC20 for IERC20;

    bool public useStatic;

    uint256 public totalAllocated;
    uint256 public maximumAllocated;

    IERC20 internal immutable _FTSO;
    IERC20 internal immutable _RESERVE;
    IGHST internal immutable _GHST;
    ITreasury internal immutable _TREASURY;
    IStaking internal immutable _STAKING;
    address internal immutable _DAO;

    mapping(address => Term) private _terms;
    mapping(address => address) public walletChange;

    constructor(
        address _ftsoAddress,
        address _reserveAddress,
        address _treasuryAddress,
        address _daoAddress,
        address _ghstAddress,
        address _stakingAddress,
        uint _maximumAllocated
    ) Ownable(msg.sender) {
        maximumAllocated = _maximumAllocated;
        useStatic = true;

        _FTSO = IERC20(_ftsoAddress);
        _RESERVE = IERC20(_reserveAddress);
        _TREASURY = ITreasury(_treasuryAddress);
        _GHST = IGHST(_ghstAddress);
        _STAKING = IStaking(_stakingAddress);
        _DAO = _daoAddress;
    }

    function claim(uint256 amount) external {
        _FTSO.safeTransfer(msg.sender, _claim(amount));
    }

    function stake(
        address to,
        uint256 amount,
        bool isRebasing,
        bool isClaim
    ) external {
        uint256 toStake = _claim(amount);
        //
        // _FTSO.approve(address(_STAKING), toStake);
        // _STAKING.stake(toStake, msg.sender);
        //
        // if (isFtso) _STAKING.claim(msg.sender);
        _STAKING.stake(toStake, to, isRebasing, isClaim);
    }

    function _claim(uint256 amount) internal returns (uint256 toSend) {
        Term memory info = _terms[msg.sender];

        _RESERVE.safeTransferFrom(msg.sender, address(this), amount);
        _RESERVE.approve(address(_TREASURY), amount);
        toSend = _TREASURY.deposit(address(_RESERVE), amount, 0);

        if (redeemableFor(msg.sender) / 1e9 < toSend) revert NotEnoughVested();
        if (info.max - claimed(msg.sender) < toSend) revert ClaimedOverMax();

        if (useStatic) {
            _terms[msg.sender].gClaimed = info.gClaimed + _GHST.balanceTo(toSend * 9 / 10);
            _terms[msg.sender].claimed = info.claimed + (toSend / 10);
        } else {
            _terms[msg.sender].gClaimed = info.gClaimed + _GHST.balanceTo(toSend);
        }
    }

    function pushWalletChange(address newAddress) external {
        if (_terms[msg.sender].percent == 0) revert PercentEmpty();
        walletChange[msg.sender] = newAddress;
    }

    function pullWalletChange(address someAddress) external {
        if (walletChange[someAddress] != msg.sender) revert WalletNotPushed();
        walletChange[someAddress] = address(0);
        _terms[msg.sender] = _terms[someAddress];
        delete _terms[someAddress];
    }

    function terms(address some) public view returns (Term memory) {
        return _terms[some];
    }

    function redeemableFor(address someAddress) public view returns (uint256) {
        uint max = circulatingSupply() * _terms[someAddress].percent * 1e3;
        return (max - claimed(someAddress)) * 1e9;
    }

    function claimed(address someAddress) public view returns (uint256) {
        return _GHST.balanceFrom(_terms[someAddress].gClaimed)
            + _terms[someAddress].claimed;
    }

    function circulatingSupply() public view returns ( uint ) {
        return _TREASURY.baseSupply() - _FTSO.balanceOf(_DAO);
    }

    function setTerms(
        address _address,
        uint256 _percent,
        uint256 _claimed,
        uint256 _gClaimed,
        uint256 _max
    ) public onlyOwner {
        if (_terms[_address].max != 0) revert AddressExists();
        _terms[_address] = Term({
            percent: _percent,
            claimed: _claimed,
            gClaimed: _gClaimed,
            max: _max
        });
        totalAllocated = totalAllocated + _percent;
        if (totalAllocated > maximumAllocated) revert TooMuch();
    }

    function treatAllAsStaked() external {
        if (msg.sender != _DAO) revert NotADao();
        useStatic = false;
    }
}
