// File: Governance.sol



// SPDX-License-Identifier: Apache-2.0
// 2021 (C) SUPER HOW Contracts: superhow.ART Auction governance v1.0 

pragma solidity 0.8.4;

import "./Weighting.sol";
import "./IERC20.sol";
import "./IERC1155.sol";


interface IERC1155extended is IERC1155 {
    
    function mint(address account, uint256 id, uint256 amount, bytes memory data) external;

}

contract Governance {
    
    uint256 internal _idNFT = 0;
    address internal _governanceBeneficiary;
    address internal _whaleAddress;
    address internal _shrimpAddress;
    address internal _erc1155ContractAddress;
    uint internal _winner;
    uint internal _userAmount;
    uint internal _shrimpTransferAmount;
    uint internal _governanceAuctionEndTime;
    uint internal _totalGovernance;
    uint internal _firstDistributionCount;
    uint internal _lastDistributionCount;
    bool internal _distributedAllNFT;
    IERC1155extended internal _erc1155ContractInstance;


    modifier onlyGovernanceOwner() {
        require(msg.sender == _governanceBeneficiary, "Only the owner can use this function");
        _;
    }
    
    modifier afterGovernanceAuction() {
        require(block.timestamp >= _governanceAuctionEndTime, "Auction time is incorrect");
        _; 
    }
    
    modifier distributedAllNFT() {
        require(false == _distributedAllNFT, "NFT's have been distributed to all users");
        _;
    }
   
    constructor(
        address governanceBeneficiary
        ){
        _governanceBeneficiary = governanceBeneficiary;
        _firstDistributionCount = 0;
        _lastDistributionCount = 100;
        _distributedAllNFT = false;
    }
    
    function distributeNFTToWinner(Weighting weightingContract, Whales whalesContract, Shrimps shrimpsContract)
        public
        distributedAllNFT()
        onlyGovernanceOwner()
    {
        _governanceAuctionEndTime = weightingContract.getAuctionEndTime();
        _winner = weightingContract._passWinner();
        require(_winner != 0, "Winner has not been anounced yet or auction has failed to raise funds");
        require(_winner != 3, "Minumum has not been reached for the sale of the painting");
        if(_winner == 1){
            _distributeToWhale(whalesContract);
        }else if (_winner == 2){
            _distributeToShrimps(shrimpsContract);
        }
    }
    
    function _distributeToShrimps(Shrimps shrimpsContract)
        internal
        afterGovernanceAuction()
    {       
            _totalGovernance = shrimpsContract.getTotalBid() / 1e17 wei;
            require(_totalGovernance > 0, "Shrimp bid amount was 0 or less");
            if(_firstDistributionCount == 0){
                _erc1155ContractInstance.mint(_governanceBeneficiary, _idNFT, _totalGovernance, "");
            }
            _userAmount = shrimpsContract.getTotalAmountOfShrimps();
            if (_lastDistributionCount > shrimpsContract.getTotalAmountOfShrimps()) {
                _lastDistributionCount = shrimpsContract.getTotalAmountOfShrimps();
            }
            for (uint j = _firstDistributionCount; j < _lastDistributionCount; j++) {
                _shrimpAddress = shrimpsContract.getShrimpAddress(j);
                _shrimpTransferAmount = shrimpsContract.getShrimpBid(_shrimpAddress);
                require(_shrimpTransferAmount > 0, "Shrimp bid amount must be higher than 0");
                _shrimpTransferAmount = _shrimpTransferAmount / 1e17 wei;
                _erc1155ContractInstance.safeTransferFrom(_governanceBeneficiary, _shrimpAddress, _idNFT, _shrimpTransferAmount, "");
            }
            _firstDistributionCount = _lastDistributionCount;
            _lastDistributionCount += 100;
            if (_firstDistributionCount >= shrimpsContract.getTotalAmountOfShrimps()) {
                _distributedAllNFT = true;
            }
    }
    
    function _distributeToWhale(Whales whalesContract)
        internal
        afterGovernanceAuction()
    {
            _userAmount = 1;
            _totalGovernance = 1;
            _whaleAddress = whalesContract.getHighestBidder();
            _erc1155ContractInstance.mint(_whaleAddress, _idNFT, _userAmount, "");
            
            // _erc1155ContractInstance.safeTransferFrom(_governanceBeneficiary, _whaleAddress, 0, _userAmount, "");
            _distributedAllNFT = true;
    }
    
    function governanceTransfer(address recipient, uint256 amount)
        public 
        onlyGovernanceOwner()
        afterGovernanceAuction()
        returns (bool) 
    {
        _governanceTransfer(msg.sender, recipient, amount);
        return true;
    }
    
    function _governanceTransfer(address sender, address recipient, uint256 amount) 
        internal
        virtual 
    {
        require(sender != address(0), "Transfer from the zero address");
        require(recipient != address(0), "Transfer to the zero address");
        uint256 senderBalance = address(this).balance;
        require(senderBalance >= amount, "Transfer amount exceeds balance");

        payable(recipient).transfer(amount);
    }
    
    function setERC1155contract(address erc1155ContractAddress) 
        public
        onlyGovernanceOwner()
    {
        _erc1155ContractAddress = erc1155ContractAddress;
        // creates erc1155 interface with set ERC1155 address
        _erc1155ContractInstance = IERC1155extended(erc1155ContractAddress);
    }
    
    function setIdNFT(uint256 idNFT) 
        public
        onlyGovernanceOwner()
    {
        _idNFT = idNFT;
    }
    
    function governanceWithdrawERC20ContractTokens(IERC20 tokenAddress, address recipient)
        public
        onlyGovernanceOwner()
        returns(bool)
    {
        require(msg.sender != address(0), "Sender is address zero");
        require(recipient != address(0), "Receiver is address zero");
        tokenAddress.approve(address(this), tokenAddress.balanceOf(address(this)));
        if(!tokenAddress.transferFrom(address(this), recipient, tokenAddress.balanceOf(address(this)))){
            return false;
        }
        return true;
    }
    
    function getOwnedGovernanceAmount(address userAddress) 
        public
        view
        afterGovernanceAuction()
        returns (uint)
    {
        require(userAddress != address(0), "User address is zero");
        require(_totalGovernance != 0, "Governance has yet to be distributed");
        uint ownedGovernanceAmount = _erc1155ContractInstance.balanceOf(userAddress, _idNFT);
        return ownedGovernanceAmount;
    }
    function getERC1155contract() 
        public
        view
        returns (address)
    {
        return _erc1155ContractAddress;
    }
    
    function getIdNFT() 
        public
        view
        returns (uint256)
    {
        return _idNFT;
    }
    
    
    function getTotalGovernanceAmount() 
        public
        view
        afterGovernanceAuction()
        returns (uint)
    {
        require(_totalGovernance != 0, "Governance has yet to be distributed");
        return _totalGovernance;
    }

    function getGovernanceBeneficiary() 
        public
        view
        returns (address)
    {
        return _governanceBeneficiary;
    }
    
}

// File: IERC1155.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.3.2 (token/ERC1155/IERC1155.sol)

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Required interface of an ERC1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155 is IERC165 {
    /**
     * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

    /**
     * @dev Returns the amount of tokens of token type `id` owned by `account`.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
        external
        view
        returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the caller.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must be have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external;
}

// File: IERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.3.2 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// File: IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.3.2 (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

// File: Shrimps.sol



// SPDX-License-Identifier: Apache-2.0
// 2021 (C) SUPER HOW Contracts: superhow.ART Shrimps pool v1.0

pragma solidity 0.8.4;

import "./Weighting.sol";
import "./IERC20.sol";


contract Shrimps {

    address payable internal _shrimpBeneficiary;
    address internal _shrimpWeightingContract;
    uint internal _totalBid;
    uint internal _maximumShrimpBid;
    uint internal _minimumShrimpBid;
    uint internal _shrimpLastBidTime;
    uint internal _shrimpIncrement;
    uint internal _shrimpReturnFee;
    uint internal _firstDistributionCount;
    uint internal _lastDistributionCount;
    address[] internal shrimpAddresses;   
    bool internal _returnedAllFunds;
    bool private constant NOT_ENTERED = false;
    bool private constant ENTERED = true;
    Weighting scon;
    
    mapping(address => uint) internal _shrimpArray;
    mapping(address => bool) internal _shrimpBid;
    mapping(address => bool) internal _userShrimpReentrancy;
    
    receive() external payable {}
    fallback() external payable {}
    
    modifier shrimpMultipleETH(){
      require(msg.value % _shrimpIncrement * 10**18 == 0, "Only 0,1 ETH multiples are accepted"); //0.1 ether
       _;
    }
   
    modifier beforeShrimpAuction(uint _time) {
        require(block.timestamp < _time, "Shrimp auction has ended");
        _;
    }
    
    modifier afterShrimpAuction(uint _time) {
        require(block.timestamp >= _time, "Shrimp auction did not end yet");
        _;
    }
    
    modifier notShrimpOwner() {
        require(msg.sender != _shrimpBeneficiary, "Contract owner can not interract here");
        _;
    }
    
    modifier onlyShrimpOwner(address messageSender) {
        require(_shrimpBeneficiary == messageSender, "Only the owner can use this function");
        _;
    }
    
    modifier isShrimpWeighting() {
        require(address(_shrimpWeightingContract) == msg.sender, "Wrong contract passed. Contract is not Weighting");
        _;
    }
    
    modifier highestBidMaximum() {
        require(msg.value <= _maximumShrimpBid, "Bid must be less than the set maximum bid");
        require((_shrimpArray[msg.sender] + msg.value) <= _maximumShrimpBid, "Bid must be less than the set maximum bid");
        _;
    }
    
    modifier lowestShrimpBidMinimum() {
        require(msg.value >= _minimumShrimpBid/(1 ether), "Minimum bid amount required");
        _;
    }
    
    modifier returnedAllFunds() {
        require(false == _returnedAllFunds, "Funds have been returned to all users");
        _;
    }
    
    modifier nonShrimpReentrant() {
        require(_userShrimpReentrancy[msg.sender] != ENTERED, "ReentrancyGuard: reentrant call");
        _userShrimpReentrancy[msg.sender] = ENTERED;
        _;
        _userShrimpReentrancy[msg.sender] = NOT_ENTERED;
    }

    constructor(
        address payable shrimpBeneficiary,
        uint maximumShrimpBid,
        uint minimumShrimpBid,
        address shrimpWeightingContract
    )
    {
        _shrimpBeneficiary = shrimpBeneficiary;
        _maximumShrimpBid = maximumShrimpBid;
        _minimumShrimpBid = minimumShrimpBid;
        _shrimpWeightingContract = shrimpWeightingContract;
        scon = Weighting(payable(_shrimpWeightingContract));
        _shrimpIncrement = 0.1 ether;
        _shrimpReturnFee = 0.001 ether;
        _firstDistributionCount = 0;
        _lastDistributionCount = 100;
        _returnedAllFunds = false;
    }
    
    function shrimpsBid()
        public
        payable
        beforeShrimpAuction(scon.getAuctionEndTime())
        notShrimpOwner()
        highestBidMaximum()
        lowestShrimpBidMinimum()
        shrimpMultipleETH()
        nonShrimpReentrant()
    {
        _shrimpsBid();
    }
   
    function _shrimpsBid()
        internal
    {
        uint amount = msg.value;
        
        if(amount > 0){
            _shrimpArray[msg.sender] = _shrimpArray[msg.sender] + amount;
            if(_shrimpBid[msg.sender] == false){
                _shrimpBid[msg.sender] = true;
                shrimpAddresses.push(msg.sender);
            }
            _totalBid = _totalBid + amount;
            _shrimpLastBidTime = block.timestamp;
            
            uint timeLeft = scon.getAuctionEndTime() - block.timestamp;
            if(timeLeft <= 15 minutes){
                scon.setAuctionEndTime();
            }
        }
    }

    function _returnShrimpFunds()
        external
        payable
        isShrimpWeighting()
        returnedAllFunds()
        afterShrimpAuction(scon.getAuctionEndTime())
    {
        if (_lastDistributionCount > shrimpAddresses.length) {
            _lastDistributionCount = shrimpAddresses.length;
        }
        for (uint j = _firstDistributionCount; j < _lastDistributionCount; j++) {
            if(_shrimpArray[shrimpAddresses[j]] > 0){
                uint amount = _shrimpArray[shrimpAddresses[j]] - _shrimpReturnFee;
                if(payable(shrimpAddresses[j]).send(amount)){
                    _shrimpArray[shrimpAddresses[j]] = 0;
                }
            }
        }
        _firstDistributionCount = _lastDistributionCount;
        _lastDistributionCount += 100;
        if (_firstDistributionCount >= shrimpAddresses.length) {
            _returnedAllFunds = true;
        }
    }
    
    function transferShrimpOwnership(address payable newShrimpBeneficiary)
        public
        onlyShrimpOwner(msg.sender)
        beforeShrimpAuction(scon.getAuctionEndTime())
    {
        _transferShrimpOwnership(newShrimpBeneficiary);
    }
   
    function _transferShrimpOwnership(address payable newShrimpBeneficiary)
        internal
    {
        require(newShrimpBeneficiary != address(0));
        _shrimpBeneficiary = newShrimpBeneficiary;  
    }
    
    function transferShrimpOwnershipToZero()
        public
        onlyShrimpOwner(msg.sender)
        beforeShrimpAuction(scon.getAuctionEndTime())
    {
        _transferShrimpOwnershipToZero();
    }
   
    function _transferShrimpOwnershipToZero()
        internal
    {
        _shrimpBeneficiary = payable(address(0));  
    }
    
    function shrimpsTransfer(address recipient, uint256 amount)
        public 
        onlyShrimpOwner(msg.sender)
        afterShrimpAuction(scon.getAuctionEndTime())
        returns (bool) 
    {   
        _shrimpsTransfer(msg.sender, recipient, amount);
        return true;
    }
    
    function _shrimpsTransfer(address sender, address recipient, uint256 amount) 
        internal
        virtual 
    {
        require(sender != address(0), "Transfer from the zero address");
        require(recipient != address(0), "Transfer to the zero address");
        uint256 senderBalance = address(this).balance;
        require(senderBalance >= amount, "Transfer amount exceeds balance");

        if(_shrimpBid[recipient] == true){
            _shrimpArray[recipient] = _shrimpArray[recipient] - amount;
            _totalBid = _totalBid - amount;
            if(_shrimpArray[recipient] == 0){
                delete _shrimpArray[recipient];
            }
        }
        payable(recipient).transfer(amount);
    }
    
    function shrimpsWithdrawERC20ContractTokens(IERC20 tokenAddress, address recipient)
        public
        onlyShrimpOwner(msg.sender)
        returns(bool)
    {
        require(msg.sender != address(0), "Sender is address zero");
        require(recipient != address(0), "Receiver is address zero");
        tokenAddress.approve(address(this), tokenAddress.balanceOf(address(this)));
        if(!tokenAddress.transferFrom(address(this), recipient, tokenAddress.balanceOf(address(this)))){
            return false;
        }
        return true;
    }
    
    function resetShrimpReentrancy(address user) 
        public
        onlyShrimpOwner(msg.sender)
    {
        _userShrimpReentrancy[user] = NOT_ENTERED;
    }

    function getShrimpBeneficiary() 
        public
        view
        returns (address)
    {
        return _shrimpBeneficiary;
    }
    
    function getShrimpAuctionEndTime() 
        public
        view
        returns (uint) 
    {
        return scon.getAuctionEndTime();
    }
    
    function getTotalBid() 
        public
        view
        returns (uint)
    {
        return _totalBid;
    }
    
    function getShrimpReturnFee() 
        public
        view
        returns (uint256) 
    {
        return _shrimpReturnFee;
    }
    
    function getMaximumShrimpBid() 
        public
        view
        returns (uint) 
    {
        return _maximumShrimpBid;
    }
    
    function getMinimumShrimpBid() 
        public
        view
        returns (uint) 
    {
        return _minimumShrimpBid;
    }
    
    function checkIfShrimpExists(address shrimp) 
        public
        view
        returns (bool)
    {
        if(_shrimpBid[shrimp] == true){
            return true;
        }
        return false;
    }
    
    function getShrimpBid(address shrimpAddress) 
        public
        view
        returns (uint) 
    {
        return _shrimpArray[shrimpAddress];
    }
    
    function getShrimpReentrancyStatus(address user) 
        public
        view
        onlyShrimpOwner(msg.sender)
        returns (bool) 
    {
        return _userShrimpReentrancy[user];
    }
    
    function getTotalAmountOfShrimps() 
        public
        view
        returns (uint) 
    {
        return shrimpAddresses.length;
    }
    
    function getShrimpLastBidTime()
        public
        view
        returns (uint)
    {
        return _shrimpLastBidTime;
    }
    
    function getShrimpIncrement() 
        public
        view
        returns (uint) 
    {
        return _shrimpIncrement;
    }
    
    function getShrimpAddress(uint id) 
        public
        view
        returns (address)
    {
        return shrimpAddresses[id];
    }
    
    function getShrimpContractAddress() 
        public
        view
        returns (address)
    {
        return address(this);
    }
    
    function getShrimpContractBalance() 
        public
        view
        returns (uint) 
    {
        return address(this).balance;
    }
    
}

// File: Weighting.sol



// SPDX-License-Identifier: Apache-2.0
// 2021 (C) SUPER HOW Contracts: superhow.ART Auction engine v1.0

pragma solidity 0.8.4;

import "./Whales.sol";
import "./Shrimps.sol";
import "./IERC20.sol";


contract Weighting {
    
    address payable internal _weightingBeneficiary;
    address internal _highestWhaleBidder;
    address internal _winnerContract;
    address internal _shrimpsContractAddress;
    address internal _whalesContractAddress;
    uint internal _highestWhaleBid;
    uint internal _highestShrimpBid;
    uint internal _whaleBidTime;
    uint internal _shrimpBidTime;
    uint internal _auctionEndTime;  
    uint internal _paintingMinimalPricing;
    uint internal _weightingWinner;
    uint internal _weightingWinnerMidAuction;
    bool internal _whaleWinnerCheck;
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    receive() external payable {}
    fallback() external payable {}

    modifier onlyOwner() {
        require(msg.sender == _weightingBeneficiary, "Only the owner can use this function");
        _;
    }
    
    modifier afterAuction() {
        require(block.timestamp >= _auctionEndTime, "Auction time is incorrect");
        _;
    }
    
    modifier beforeAuction() {
        require(block.timestamp < _auctionEndTime, "Auction time is incorrect");
        _;
    }
    modifier onlyShrimpsOrWhales() {
        require(msg.sender == _whalesContractAddress || msg.sender == _shrimpsContractAddress, "Not shrimps or whales contract calling");
        _;
    }
    
   
    constructor(
        address payable weightingBeneficiary,
        uint paintingMinimalPricing,
        uint auctionEndTime
    )
    {
        _weightingBeneficiary = weightingBeneficiary;
        _paintingMinimalPricing = paintingMinimalPricing;
        _auctionEndTime = auctionEndTime;
        
        _weightingWinner = 0;
        _weightingWinnerMidAuction = 0;
        _whaleWinnerCheck = false;
    }
   
    function returnWhaleFunds(Whales whalesContract) 
        public
        payable
        onlyOwner()
        afterAuction()
    {
        whalesContract._returnWhaleFunds();
    }
    
    function returnShrimpFunds(Shrimps shrimpContract) 
        public
        payable
        onlyOwner()
        afterAuction()
    {
        shrimpContract._returnShrimpFunds();
    }
    
    function determineWinner(Whales whalesContract, Shrimps shrimpContract)
        public
        onlyOwner()
        afterAuction()
        returns(uint)
    {
        _highestWhaleBid = whalesContract.getHighestBid();
        _highestShrimpBid = shrimpContract.getTotalBid();
        
        _whaleBidTime = whalesContract.getWhaleLastBidTime();
        _shrimpBidTime = shrimpContract.getShrimpLastBidTime();
    
        if(_highestShrimpBid < _paintingMinimalPricing && _highestWhaleBid < _paintingMinimalPricing){
            _weightingWinner = 3;
            return(_weightingWinner);
        }else{
            if(_highestWhaleBid > _highestShrimpBid){
                _weightingWinner = 1;
                _winnerContract = address(whalesContract);
            }else if (_highestWhaleBid < _highestShrimpBid){
                _weightingWinner = 2;
                _winnerContract = address(shrimpContract);
            }else{
                if(_whaleBidTime < _shrimpBidTime){
                    _weightingWinner = 1;
                    _winnerContract = address(whalesContract);
                }else{
                    _weightingWinner = 2;
                    _winnerContract = address(shrimpContract);
                }
            }
        }

        return(_weightingWinner);
    }
    
    function determineWinnerMidAuction(Whales whalesContract, Shrimps shrimpContract)
        public
        returns(uint, uint)
    {
        uint _highestWinnerBidMidAuction;
        _highestWhaleBid = whalesContract.getHighestBid();
        _highestShrimpBid = shrimpContract.getTotalBid();
        
        _whaleBidTime = whalesContract.getWhaleLastBidTime();
        _shrimpBidTime = shrimpContract.getShrimpLastBidTime();
        
        if(_highestWhaleBid > _highestShrimpBid){
            _weightingWinnerMidAuction = 1;
            _highestWinnerBidMidAuction = _highestWhaleBid;
        }else if (_highestWhaleBid < _highestShrimpBid){
            _weightingWinnerMidAuction = 2;
            _highestWinnerBidMidAuction = _highestShrimpBid;
        }else{
            if(_whaleBidTime < _shrimpBidTime){
                _weightingWinnerMidAuction = 1;
                _highestWinnerBidMidAuction = _highestWhaleBid;
            }else{
                _weightingWinnerMidAuction = 2;
                _highestWinnerBidMidAuction = _highestShrimpBid;
            }
        }
        return(_weightingWinnerMidAuction, _highestWinnerBidMidAuction);
    }
    
    function weightingTransfer(address recipient, uint256 amount)
        public 
        onlyOwner()
        afterAuction()
        returns (bool) 
    {
        _weightingTransfer(msg.sender, recipient, amount);
        return true;
    }
    
    function _weightingTransfer(address sender, address recipient, uint256 amount) 
        internal
        virtual 
    {
        require(sender != address(0), "Transfer from the zero address");
        require(recipient != address(0), "Transfer to the zero address");
        uint256 senderBalance = address(this).balance;
        require(senderBalance >= amount, "Transfer amount exceeds balance");

        payable(recipient).transfer(amount);
    }
    
    function weightingWithdrawERC20ContractTokens(IERC20 tokenAddress, address recipient)
        public
        onlyOwner()
        returns(bool)
    {
        require(msg.sender != address(0), "Sender is address zero");
        require(recipient != address(0), "Receiver is address zero");
        tokenAddress.approve(address(this), tokenAddress.balanceOf(address(this)));
        if(!tokenAddress.transferFrom(address(this), recipient, tokenAddress.balanceOf(address(this)))){
            return false;
        }
        return true;
    }
    
    function setAuctionEndTime() 
        external
        onlyShrimpsOrWhales()
    {
        _auctionEndTime = block.timestamp + 15 minutes;
    }
    
    function setPaintingMinimalPricing(uint newPrice) 
        public
        onlyOwner()
    {
        _paintingMinimalPricing = newPrice;
    }
    
    function setAuctionEndTimeManual(uint256 newtime) 
        public
        onlyOwner()
    {
        _auctionEndTime = newtime;
    }

    function confirmWhaleWinner()
        public
        afterAuction()
    {
        require(msg.sender == _highestWhaleBidder, "False winner interaction");
        _whaleWinnerCheck = true;
    }
    
    function setWhalesContract(address whalesContractAddress) 
        public
        onlyOwner()
    {
        _whalesContractAddress = whalesContractAddress;
    }
    
    function setShrimpsContract(address shrimpsContractAddress) 
        public
        onlyOwner()
    {
        _shrimpsContractAddress = shrimpsContractAddress;
    }

    function _passWinner()
        external
        view
        afterAuction()
        returns(uint)
    {
        return (_weightingWinner);
    }
    
    function getWhaleData(Whales whalesContract)
        public
        onlyOwner()
        returns (address, uint)
    {
        _highestWhaleBidder = whalesContract.getHighestBidder();
        _highestWhaleBid = whalesContract.getHighestBid();
        return (_highestWhaleBidder, _highestWhaleBid);
    }
    
    function getShrimpData(Shrimps shrimpContract)
        public
        onlyOwner()
        returns (uint)
    {
        _highestShrimpBid = shrimpContract.getTotalBid();
        return (_highestShrimpBid);
    }
    
    function getBeneficiary() 
        public
        view
        returns (address)
    {
        return _weightingBeneficiary;
    }
    
    function getHighestWhaleBidder() 
        public
        view
        returns (address) 
    {
        return _highestWhaleBidder;
    }
    
    function getWinnerContract() 
        public
        view
        returns (address)
    {
        return _winnerContract;
    }
    
    function getHighestWhaleBid() 
        public
        view
        returns (uint) 
    {
        return _highestWhaleBid;
    }
    
    function getHighestShrimpBid() 
        public
        view
        returns (uint) 
    {
        return _highestShrimpBid;
    }
    
    function getAuctionEndTime() 
        public
        view
        returns (uint) 
    {
        return _auctionEndTime;
    }
    
    function whaleWinnerContractInteraction() 
        public
        afterAuction()
        view
        returns (bool) 
    {
        return _whaleWinnerCheck;
    }
 
    function getWeightingContractAddress() 
        public
        view
        returns (address) 
    {
        return address(this);
    }
    
    function getShrimpsContractAddress() 
        public
        view
        returns (address) 
    {
        return _shrimpsContractAddress;
    }
    
    function getWhalesContractAddress() 
        public
        view
        returns (address) 
    {
        return _whalesContractAddress;
    }
    
}

// File: Whales.sol



// SPDX-License-Identifier: Apache-2.0
// 2021 (C) SUPER HOW Contracts: superhow.ART Whales bidding v1.0

pragma solidity 0.8.4;

import "./Weighting.sol";
import "./IERC20.sol";


contract Whales {
   
    address payable internal _whaleBeneficiary;
    address internal _whaleWeightingContract;
    address internal _whaleHighestBidder;
    uint internal _highestBid;
    uint internal _whaleLastBidTime;
    uint internal _minimumWhaleBid;
    uint internal _whaleIncrement;
    uint internal _whaleReturnFee;
    bool private constant NOT_ENTERED = false;
    bool private constant ENTERED = true;
    Weighting wcon;

    receive() external payable {}
    fallback() external payable {}
   
    mapping(address => uint) internal _pendingReturns;
    mapping(address => bool) internal _userWhaleReentrancy;
    
    modifier whaleMultipleETH(){
       require(msg.value % _whaleIncrement == 0, "Only 1 ETH multiples are accepted"); //1 ether
       _;
    }
   
    modifier beforeWhaleAuction(uint _time) {
        require(block.timestamp < _time, "Whale auction has ended");
        _;
    }
    
    modifier afterWhaleAuction(uint _time) {
        require(block.timestamp >= _time, "Whale auction did not end yet");
        _;
    }
    
    modifier notWhaleOwner() {
        require(msg.sender != _whaleBeneficiary, "Contract owner can not interract here");
        _;
    }
    
    modifier onlyWhaleOwner(address messageSender) {
        require(_whaleBeneficiary == messageSender, "Only the owner can use this function");
        _;
    }
    
    modifier isWhaleWeighting() {
        require(address(_whaleWeightingContract) == msg.sender, "Wrong contract passed. Contract is not Weighting");
        _;
    }
    
    modifier cantBeFutureOwner(address newBeneficiaryW) {
        require(_whaleHighestBidder != newBeneficiaryW, "Can not pass ownership to highest bidder");
        _;
    }
    
    modifier lowestWhaleBidMinimum() {
        require(msg.value >= _minimumWhaleBid/(1 ether), "Minimum bid amount required");
        _;
    }
    
    modifier highestBidRequired() {
        require(msg.value > _highestBid, "Highest bidder amount required");
        _;
    }
    
    modifier sameBidder() {
        require(msg.sender == _whaleHighestBidder, "Only the same bidder can add to amount");
        _;   
    }
    
    modifier nonWhaleReentrant() {
        require(_userWhaleReentrancy[msg.sender] != ENTERED, "ReentrancyGuard: reentrant call");
        _userWhaleReentrancy[msg.sender] = ENTERED;
        _;
        _userWhaleReentrancy[msg.sender] = NOT_ENTERED;
    }

    constructor(
        address payable whaleBeneficiary,
        uint minimumWhaleBid,
        address whaleWeightingContract
    )
    {
        _whaleBeneficiary = whaleBeneficiary;
        _minimumWhaleBid = minimumWhaleBid;
        _whaleWeightingContract = whaleWeightingContract;
        wcon = Weighting(payable(_whaleWeightingContract));
        
        _whaleIncrement = 1 ether;
        _whaleReturnFee = 0.01 ether;
    }
    
    function whalesBid()
        public
        payable
        beforeWhaleAuction(wcon.getAuctionEndTime())
        notWhaleOwner()
        lowestWhaleBidMinimum()
        highestBidRequired()
        whaleMultipleETH()
        nonWhaleReentrant()
    {
        _whalesBid();
    }
   
    function _whalesBid()
        internal
    {
        if (_highestBid != 0) {
            _pendingReturns[_whaleHighestBidder] = _pendingReturns[_whaleHighestBidder] + _highestBid;
            uint amount = _pendingReturns[_whaleHighestBidder];
            if (amount > 0) {
                _pendingReturns[_whaleHighestBidder] = 0;
                if (!payable(_whaleHighestBidder).send(amount)) {
                    _pendingReturns[_whaleHighestBidder] = amount;
                }
            }
        }
        
        _whaleHighestBidder = msg.sender;
        _highestBid = msg.value;
        
        uint timeLeft = wcon.getAuctionEndTime() - block.timestamp;
        if(timeLeft <= 15 minutes){
            wcon.setAuctionEndTime();
        }
        _whaleLastBidTime = block.timestamp;
    }
    
    function addToWhalesBid()
        public
        payable
        beforeWhaleAuction(wcon.getAuctionEndTime())
        notWhaleOwner()
        sameBidder()
        whaleMultipleETH()
        nonWhaleReentrant()
    {
        _addToBid();
    }
    
    function _addToBid()
        internal
    {
        if(msg.value > 0 && _highestBid > 0){
            _highestBid = _highestBid + msg.value;
            _whaleLastBidTime = block.timestamp;
        }
    }

    function _returnWhaleFunds() 
        external
        payable
        isWhaleWeighting()
        afterWhaleAuction(wcon.getAuctionEndTime())
    {
        if (_highestBid > 0) {
            uint amount =  address(this).balance - _whaleReturnFee;
            payable(_whaleHighestBidder).transfer(amount);
        }
    }
    
    function transferWhaleOwnership(address payable newWhaleBeneficiary)
        public
        onlyWhaleOwner(msg.sender)
        beforeWhaleAuction(wcon.getAuctionEndTime())
        cantBeFutureOwner(newWhaleBeneficiary)
    {
        _transferWhaleOwnership(newWhaleBeneficiary);
    }
   
    function _transferWhaleOwnership(address payable newWhaleBeneficiary)
        internal
    {
        require(newWhaleBeneficiary != address(0));
        _whaleBeneficiary = newWhaleBeneficiary;  
    }
    
        
    function transferWhaleOwnershipToZero()
        public
        onlyWhaleOwner(msg.sender)
        beforeWhaleAuction(wcon.getAuctionEndTime())
    {
        _transferWhaleOwnershipToZero();
    }
   
    function _transferWhaleOwnershipToZero()
        internal
    {
        _whaleBeneficiary = payable(address(0));  
    }
    
    function whalesTransfer(address recipient, uint256 amount)
        public 
        onlyWhaleOwner(msg.sender)
        afterWhaleAuction(wcon.getAuctionEndTime())
        returns (bool) 
    {
        _whalesTransfer(msg.sender, recipient, amount);
        return true;
    }
    
    function _whalesTransfer(address sender, address recipient, uint256 amount) 
        internal
        virtual 
    {
        require(sender != address(0), "Transfer from the zero address");
        require(recipient != address(0), "Transfer to the zero address");
        uint256 senderBalance = address(this).balance;
        require(senderBalance >= amount, "Transfer amount exceeds balance");
        
        if(recipient == _whaleHighestBidder){
            _highestBid = _highestBid - amount;
            if(_highestBid == 0){
                _whaleHighestBidder = address(0);
            }
        }
        payable(recipient).transfer(amount);
    }
    
    function whalesWithdrawERC20ContractTokens(IERC20 tokenAddress, address recipient)
        public
        onlyWhaleOwner(msg.sender)
        returns(bool)
    {
        require(msg.sender != address(0), "Sender is address zero");
        require(recipient != address(0), "Receiver is address zero");
        tokenAddress.approve(address(this), tokenAddress.balanceOf(address(this)));
        if(!tokenAddress.transferFrom(address(this), recipient, tokenAddress.balanceOf(address(this)))){
            return false;
        }
        return true;
    }
    
    function resetWhaleReentrancy(address user) 
        public
        onlyWhaleOwner(msg.sender)
    {
        _userWhaleReentrancy[user] = NOT_ENTERED;
    }
    
    function getPendingReturns(address user) 
        public
        view
        onlyWhaleOwner(msg.sender)
        returns (uint256) 
    {
        return _pendingReturns[user];
    }
    
    function getWhaleReturnFee() 
        public
        view
        returns (uint256) 
    {
        return _whaleReturnFee;
    }
    
    function getWhaleReentrancyStatus(address user) 
        public
        view
        onlyWhaleOwner(msg.sender)
        returns (bool) 
    {
        return _userWhaleReentrancy[user];
    }

    function getWhaleBeneficiary() 
        public
        view
        returns (address) 
    {
        return _whaleBeneficiary;
    }
    
    function getHighestBidder() 
        public
        view
        returns (address) 
    {
        return _whaleHighestBidder;
    }
    
    function getWhaleAuctionEndTime() 
        public
        view
        returns (uint)
    {
        return wcon.getAuctionEndTime();
    }
    
    function getWhaleLastBidTime()
        public
        view
        returns (uint)
    {
        return _whaleLastBidTime;
    }
    
    function getHighestBid() 
        public
        view
        returns (uint) 
    {
        return _highestBid;
    }
    
    function getWhaleIncrement() 
        public
        view
        returns (uint) 
    {
        return _whaleIncrement;
    }
    
    function getMinimumWhaleBid() 
        public
        view
        returns (uint)
    {
        return _minimumWhaleBid;
    }

    function getWhaleContractAddress() 
        public
        view
        returns (address)
    {
        return address(this);
    }
    
    function getWhaleContractBalance() 
        public
        view
        returns (uint) 
    {
        return address(this).balance;
    }
    
}