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

import "INetworkV1.sol"; // holds all interfaces
import "IStandard.sol"; // all standard Interfaces
import "Library.sol"; // Math, signedMath and Strings

import "abs_Ownable.sol";
import "abs_erc721.sol"; // main erc721

import "abs_MetaDataV1.sol"; 
import "abs_PayV1.sol";



contract drmnftv1 is ERC721, MetaDataV1, PayV1, ICountV1 {
    using Strings for uint256;

    //uint256 private constant LIMITED_MINT = 42; // the mint is unlimited
    uint256 private m_totalSupply;

    address private m_participant; // must be participant in order to play
    //// During mint, we call these two different contracts to make sure the two addresses that are provided
    //// both hold ligitimate NFTs of the project. 
    address private m_agent = address(0);
    address private m_website = address(0);

    // the commission payout constants if someone mints an agent
    uint256 private constant MIN_DISCOUNT_COUNT = 1;   // must mint 1 to get the commissions
    uint256 private constant AGENT_PERCENTAGE = 45;    // 50% of the mint price
    uint256 private constant IT_PERCENTAGE = 5;       // 5% of the mint price
    uint256 private constant INACTIVITY_DAYS = 90;     // must claim commissions within 10 days.

    uint256 private constant MINT_PENNIES = 500; // $5 to play


    // This is a fully transferable NFT that doesn't provide sendlock functionality
    bool private constant SOULBOUND = false;
    bool private constant SENDLOCK = false;

    //uint256 public constant LAST_MINTABLE_BLOCK = 26243000;

    /**
     * @dev The constructor sets the initial state of the flip mapping, position zero is not used.
     * @param _theTitled This is the project owner's wallet. Considered the owner of the project
     * @param _thePennyOracle The Penny Oracle smart contract address
     */
    constructor(address _theTitled, address _thePennyOracle, address _theParticipant, address _theAgent, address _theWebsite) 
        ERC721("drmnftv1", "DRMV1", SOULBOUND, SENDLOCK) 
        PayV1( _theTitled,  msg.sender, _thePennyOracle)
        {
            m_participant = _theParticipant;
            m_agent = _theAgent;
            m_website = _theWebsite;

            // Need to setup the commission payout structure in the PayV1 contract
            payInitalize(MINT_PENNIES, INACTIVITY_DAYS, MIN_DISCOUNT_COUNT, AGENT_PERCENTAGE, IT_PERCENTAGE);
        }


    /**
     * @dev Used to set the penny price for minting the NFT. This should only be adjustable
     * by The Titled or the Owner of the contract.
     */
    function penniesSet(uint256 _mintPennies) public onlyEither {
        iPayMintPenniesSet(_mintPennies);
    }
    /**
     * @dev Used to get the penny price for using this NFT.
     */
    function penniesRead() public view returns(uint256 mint) {
        mint = iPayMintPenniesRead();
    }


    // If this contract is setup and ready for minting, this will return true.
    function IsReady() internal view returns(bool) {
        require(m_agent != address(0), "not setup");
        require(projectActive(),"Metadata not setup");
        return true;
    }


    // ------------------ ERC721 functionality ------------------ 

    /**
     * @dev returns the appropriate metadata URI for the given token id. Note that the token id
     * must exist in order for it to be built and returned. 
     */
    function tokenURI(uint256 tokenId) public view override returns (string memory) {
        _requireOwned(tokenId);
        string memory resultURI = string.concat(_baseURI(), "drmnftv1.json");
        return resultURI;
    }

    /**
     * @dev Base URI for computing {tokenURI}. 
     */
    function _baseURI() internal view override(ERC721) returns (string memory) {
        string memory resultURI = string.concat(projectBaseURI(),"metadata/");
        return resultURI;
    }

    // In order to mint, we have to make that functionality public. We will do so with a
    // function called mint() that is payable. If the checks are passed, we will call the
    // safeMint override in order to track the total minting. 
    function safeMint(address to, address _founder, address _partner, address _website) internal returns(uint256 tokenId) {
        m_totalSupply++; // used to id the tokens. 

        mintSplit(_founder, _partner, _website);

        _safeMint(to, m_totalSupply);
        return m_totalSupply;
    }

    //  ------------------ Used for IPayV1 Interface  ------------------ 

    /**
     * @dev Anyone is allowed to mint provided the metadata information has already been
     * setup, the right amount of coin is provided. There is no limit to the 
     * number of NFTs that can be minted. The caller must provide two wallets. Both 
     * wallets must hold agent NFTs
     */
    function payMint(uint256 _AgentId, uint256 _websiteId) public payable returns(uint256 tokenId) {

        require(IsReady(),"Not setup yet");
        // This is a limited mint, expire near the end of July 2024
        //require(block.number<LAST_MINTABLE_BLOCK,"exceeded minting window");
        //require(m_totalSupply <= LIMITED_MINT,"Hit Mint Limit"); // demo has no limit
        require(payValidateAmount(), "Wrong amount sent");

        // must hold a participant NFT in order to play.
        require( IERC721(m_participant).balanceOf(msg.sender) > 0, "must hold participant to play");

        // Validate that the minting code is crediting a website NFT holder
        address cutWebsiteAddr = IERC721(m_website).ownerOf(_websiteId);
        require(cutWebsiteAddr != address(0),"invalid website Id");

        // now resolve the agent id
        (address cutFounderAddr, address cutPartnerAddr) = IRegisterV1(m_agent).regResolve(_AgentId);
        require(cutFounderAddr != address(0),"Invalid agent Id");
        require(cutPartnerAddr != address(0),"Invalid agent Id");

        // Any other restrictions on minting? 
        return safeMint(msg.sender, cutFounderAddr, cutPartnerAddr, cutWebsiteAddr);
    }

    // This routine will be called by either the contract owner or The Titled in order to 
    // capture the outstanding commission from a deliquent agent. Note that the funds are
    // assigned to The Titled. 
    function paySweep(address _anAccount) external onlyEither returns(bool) {
        
        require(sweepConditions(_anAccount), "account not ready");
        return updateAbandonedAccount(_anAccount);
    }

    // There are only two ways to withdraw and they both come through here.
    function iWithdraw(address _anAccount) internal returns(bool bSuccess) {

        uint256 uiWei = claimAccount(_anAccount);
        payable(_anAccount).transfer(uiWei);
        return true;
    }
    /**
     * @dev If you have an account, you can get to your funds if all conditions are met
     */
    function payWithdraw() external returns(bool) {

        // has to pass the checks in order to withdraw.
        address recieverAddr = msg.sender;
        require(claimConditions(recieverAddr),"conditions not met");
        return iWithdraw(recieverAddr);
    }
    function payWithdrawTitled() external onlyEither returns(bool) {

        // has to pass the checks in order to withdraw.
        address recieverAddr = titled();
        require(claimConditions(recieverAddr),"conditions not met");
        return iWithdraw(recieverAddr);
    }


    /**
     * @dev Allow contract creator to extract funds. 
     */
    //function withdraw(uint amountWei) public onlyOwner returns(bool) {
    //    require(amountWei<=address(this).balance);
    //    payable(owner()).transfer(amountWei);
    //    return true;
    //}
    /**
     * @dev Allow contract creator to extract funds. 
     */
    //function withdrawAll() public onlyOwner returns(bool) {
    //    require(address(this).balance>0);
    //    payable(owner()).transfer(address(this).balance);
    //    return true;
    //}

    //  ------------------  IMetadataV1 interface ------------------ 

    /**
     * @dev This routine sets the initial state for the project metadata. The _URI is used
     * as the base URI for all token URIs. The _Project name represents the file name(without
     * extension) on the server pointed to by _URI. The _Project JSON file can be hashed to
     * produce the SHA1 hash value represented by _hash. Only the owner of the contract can
     * set the project location. If the project moves, the latest addition will be considered
     * the working location.
     */
    function projectUpdate(string calldata _URI, string calldata _Project, string calldata _hash) public onlyOwner {
        require(bytes(_URI).length > 0,"Project base URI needs valid path");
        require(bytes(_Project).length > 0,"Project name length invalid");
        require(bytes(_hash).length == 40,"requires SHA1 hash string");
        return iProjectUpdate(_URI,_Project,_hash);
    }

    //  ------------------  IPennyOracleV1 interface ------------------ 
    //
    // The Penny Oracle functionality is handled by the IPay class.
    //
    // This NFT uses the Penny Oracle. It is set in the constructor, but if it changes
    // we can update that source here.
    //
    function pennyOracleSet(address _PennyOracle) public onlyOwner {
        require(_PennyOracle != address(0), "No Penny to Reference");
        require(IERC165(_PennyOracle).supportsInterface(type(IPennyOracleV1).interfaceId),"doesn't support IPennyOracleV1");
        iPennyOracleSet(_PennyOracle);
    }

    //  ------------------  ICountV1 interface ------------------ 

    /**
     * @dev Anyone is allowed to see the membership totals
     */
    function totalSupply() external view returns(uint256 theTotalSupply) {
        return m_totalSupply;
    }


    //  ------------------  IERC165 interface ------------------ 
    //
    // When this NFT project is added to The Gallery V1, that contract will query
    // to make sure this NFT supports IMetadataV1 and IPayV1. It will not accept
    // NFT projects that don't offer this functionality.
    //
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return
            interfaceId == type(IMetadataV1).interfaceId ||
            interfaceId == type(IPayV1).interfaceId ||
            interfaceId == type(ICountV1).interfaceId ||
            super.supportsInterface(interfaceId);
    }
}
