// SPDX-License-Identifier: MIT LICENSE

pragma solidity ^0.8.17;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "./interfaces/iNft.sol";

contract Main is Ownable, Pausable, ReentrancyGuard {

    constructor() {
        _pause();
    }

    /** CONTRACTS */
    iNft public nftContract;

    /** EVENTS */
    event ManyNftsMinted(address indexed owner, uint16[] tokenIds);

    /** PUBLIC VARS */
    uint256 public MINT_PRICE = 0.09 ether;

    bool public PRE_SALE_STARTED;

    bool public PUBLIC_SALE_STARTED;
    uint8 public MAX_PUBLIC_SALE_MINTS = 3;

    address public projectWallet;

    /** PRIVATE VARS */
    mapping(address => bool) private _admins;
    mapping(address => uint8) private _preSaleAddresses;
    mapping(address => uint8) private _preSaleMints;
    mapping(address => uint8) private _publicSaleMints;
    
    /** MODIFIERS */
    modifier onlyAdmin() {
        require(_admins[_msgSender()], "Main: Only admins can call this");
        _;
    }

    modifier onlyEOA() {
        require(tx.origin == _msgSender(), "Main: Only EOA");
        _;
    }

    modifier requireVariablesSet() {
        require(address(nftContract) != address(0), "Main: Nft contract not set");
        require(address(projectWallet) != address(0), "Main: Project wallet address must be set");
        _;
    }

    /** PUBLIC FUNCTIONS */
    function mint(uint256 amount) external payable whenNotPaused nonReentrant onlyEOA {
        require(PRE_SALE_STARTED || PUBLIC_SALE_STARTED, "Main: Sale has not started yet");
        if (PRE_SALE_STARTED) {
            require(_preSaleAddresses[_msgSender()] > 0, "Main: You are not on the whitelist");
            require(_preSaleMints[_msgSender()] + amount <= _preSaleAddresses[_msgSender()], "Main: You cannot mint more during pre-sale");
        } else {
            require(_publicSaleMints[_msgSender()] + amount <= MAX_PUBLIC_SALE_MINTS, "Main: You cannot mint more");
        }
        require(msg.value >= amount * MINT_PRICE, "Main: Invalid payment amount");

        uint16[] memory tokenIds = new uint16[](amount);

        for (uint i = 0; i < amount; i++) {
            if (PRE_SALE_STARTED) {
                _preSaleMints[_msgSender()]++;
            } else {
                _publicSaleMints[_msgSender()]++;
            }

            nftContract.mint(_msgSender());
            tokenIds[i] = nftContract.totalMinted();
        }

        emit ManyNftsMinted(_msgSender(), tokenIds);
    }

    function getAllowedPreSaleMints(address addr) public view returns (uint8) {
        if (_preSaleAddresses[addr] >= _preSaleMints[addr]) {
            return _preSaleAddresses[addr] - _preSaleMints[addr];
        } 
        return 0;
    }

    function getAllowedPublicSaleMints(address addr) public view returns (uint8) {
        if (MAX_PUBLIC_SALE_MINTS >= _publicSaleMints[addr]) {
            return MAX_PUBLIC_SALE_MINTS - _publicSaleMints[addr];
        } 
        return 0;
    }

    /** ADMIN ONLY FUNCTIONS */
    function addToPresale(address[] memory addresses, uint8 allowedToMint) external onlyAdmin {
         for (uint i = 0; i < addresses.length; i++) {
            _preSaleAddresses[addresses[i]] = allowedToMint;
         }
    }

    function setMintPrice(uint256 number) external onlyAdmin {
        MINT_PRICE = number;
    }

    function setPreSaleStarted(bool started) external onlyAdmin {
        PRE_SALE_STARTED = started;
        if (PRE_SALE_STARTED) PUBLIC_SALE_STARTED = false;
    }

    function setPublicSaleStarted(bool started) external onlyAdmin {
        PUBLIC_SALE_STARTED = started;
        if (PUBLIC_SALE_STARTED) PRE_SALE_STARTED = false;
    }

    function setMaxPublicSaleMints(uint8 number) external onlyAdmin {
        MAX_PUBLIC_SALE_MINTS = number;
    }

    function isAdmin(address addr) external view onlyAdmin returns(bool) {
        if (_admins[addr]) return true;
        return false;
    }

    /** OWNER ONLY FUNCTIONS */
    function setContracts(address _nftContract) external onlyOwner {
        nftContract = iNft(_nftContract);
    }

    function setPaused(bool _paused) external requireVariablesSet onlyOwner {
        if (_paused) _pause();
        else _unpause();
    }

    function mintForTeam(address receiver, uint256 amount) external whenNotPaused onlyOwner {
        for (uint i = 0; i < amount; i++) {
            nftContract.mint(receiver);
        }
    }

    function withdraw() external onlyOwner {
        uint256 totalAmount = address(this).balance;

        bool sent;
        (sent, ) = projectWallet.call{value: totalAmount}("");
        require(sent, "Main: Failed to send funds to projectWallet");
    }

    function setProjectWallet(address addr) external onlyOwner {
        projectWallet = addr;
    }

    function addAdmin(address addr) external onlyOwner {
        _admins[addr] = true;
    }

    function removeAdmin(address addr) external onlyOwner {
        delete _admins[addr];
    }
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint16[]","name":"tokenIds","type":"uint16[]"}],"name":"ManyNftsMinted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"MAX_PUBLIC_SALE_MINTS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINT_PRICE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PRE_SALE_STARTED","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PUBLIC_SALE_STARTED","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"addAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"addresses","type":"address[]"},{"internalType":"uint8","name":"allowedToMint","type":"uint8"}],"name":"addToPresale","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getAllowedPreSaleMints","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getAllowedPublicSaleMints","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"isAdmin","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mintForTeam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"nftContract","outputs":[{"internalType":"contract iNft","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"projectWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"removeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_nftContract","type":"address"}],"name":"setContracts","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"number","type":"uint8"}],"name":"setMaxPublicSaleMints","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"number","type":"uint256"}],"name":"setMintPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_paused","type":"bool"}],"name":"setPaused","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"started","type":"bool"}],"name":"setPreSaleStarted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setProjectWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"started","type":"bool"}],"name":"setPublicSaleStarted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]