contract Vorlex  is ERC1155, ERC1155Holder,  AccessControl  {
    
    bytes32 ADMIN_ROLE = bytes32("ADMIN_ROLE");
    bytes32 MINTER_ROLE = bytes32("MINTER_ROLE");

    using Counters for Counters.Counter;
    Counters.Counter private _vorlex_count;

    /**
        * @dev Throws if called by any account other than admin.
    */
    modifier onlyAdmin() {
        require(hasRole(ADMIN_ROLE, msg.sender), "NOT_ADMIN");
        _;
    }

    enum sale_status { 
        OPEN_BUY, CANC_BUY,
        OPEN_ESCROW_BUY, CANC_ESCROW_BUY, CLOSED_ESCROW_BUY, 
        OPEN_BID, ACTIVE_BID, CANC_BID, CLOSED_BID 
    }

    struct Vorlexy {
        string ipfs;
        address custodian;
        uint256 amount;
        string vorlex_data;
    }

    struct Sale {
        sale_status status; 
        uint256 vorlex_id;
        uint256 amount;
        uint256 purchased_amount;
        uint256 settled_amount;
        uint256 worth; // Cost per Token for Buy and Total Cost for Bid
        uint256 bid_span; // Length of Bid Auction :: 1 hour for Buy Sales
        uint256 expiry; // For Bids
        address seller;
        address highest_bidder;
    }

    event InFunded(string indexed metadata);
    event OutFunded(uint256 indexed amount, address indexed receiver, 
            string indexed metadata);
    event Minted(uint256 indexed vorlex_id, string ipfs, 
            address indexed custodian, uint256 amount, string vorlex_data);
    event CustodianChanged(uint256 indexed vorlex_id, address indexed new_custodian);
    event SaleSetup(bytes32 indexed sale_id, uint256 indexed vorlex, uint256 indexed amount, 
            uint256 worth, uint256 bid_span, sale_status status);
    event Purchased(bytes32 indexed sale_id, uint256 indexed amount, 
                    address indexed buyer, bool on_chain, string metatdata);
    event Bidded(bytes32 indexed sale_id, uint256 indexed bid_amount);
    event SaleCancelled(bytes32 indexed sale_id, bool indexed by_seller);
    event SaleClosed(bytes32 indexed sale_id, uint256 indexed settle_amount);
    event Burnt(string metadata);
    event Swapped(string metadata);
    event BatchMoved(string metadata);

    uint256 public MIN_SALE = 15000000000000000;

	mapping(string => uint256) private _minted_vorlexes;
    mapping(uint256 => Vorlexy) public vorlexes;
    mapping(bytes32  => Sale) public sales;

    constructor() ERC1155("https://vorlex.co/uri/{id}.json") {
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _setupRole(ADMIN_ROLE, msg.sender);
        _setupRole(ADMIN_ROLE, address(this));
        _setupRole(MINTER_ROLE, msg.sender);
    }

    /**
        * @dev Add ETH to the contract
        * @dev Used to handle MINT FEES
        * @param metadata More Information on the Funds, Usually MINT FEES, DONATIONS or FUEL
    */
    function inFundContract(string memory metadata) external payable {
         emit InFunded(metadata);
    }

    /**
        * @dev Transfer ETH from contract :: Only by ADMIN
        * @param amount Amount of ETH to be transferred
        * @param receiver Recepient of ETH
        * @param metadata More Information on the Funds, Usually WITHDRAWAL or REFUNDS
    */

    function outFundContract(
        uint256 amount, address receiver, string memory metadata
    ) external payable onlyAdmin {
        emit OutFunded(amount, receiver, metadata);
        payable(receiver).transfer(amount); // Withdraw ETH from the Vorlex Contract
    }

    /**
        * @dev Set Minimum Price for Token for Sales 
    */

    function setMinSale(uint256 new_min_sale) 
    external virtual onlyAdmin {
        MIN_SALE = new_min_sale;
    }

    /**
        * @dev Mints a single Vorlex NFT :: Only by MINTER
        * @dev If Vorlex already exists, additional tokens are minted, no data is altered;
        * @param ipfs IPFS hash of Vorlex
        * @param custodian Vorlex Custodian :: Physical owner and custodian of vorlex who receives royalties for transaction
        * @param amount Amount of tokens to be minted for the Vorlex NFT
        * @param vorlex_data JSON Information of Vorlex
    */
	function mint(
        string memory ipfs, address custodian, 
        uint256 amount, string memory vorlex_data 
    ) external {
        require(hasRole(MINTER_ROLE, msg.sender), "NOT_MINTER");
        uint256 new_vorlex = _minted_vorlexes[ipfs];
         if(new_vorlex == 0){
            _vorlex_count.increment();
            new_vorlex = _vorlex_count.current();
            _minted_vorlexes[ipfs] = new_vorlex;
            vorlexes[new_vorlex] = Vorlexy(ipfs, custodian, amount, vorlex_data);
        }
        _mint(custodian, new_vorlex, amount, "");
        emit Minted(new_vorlex, ipfs, custodian, amount, vorlex_data);
    } 

    /**
        * @dev Set a New Custodian for a Vorlex if Former Custodian is defunct :: Only by ADMIN
        * @param vorlex_id Vorlex id
        * @param new_custodian Wallet Address of New Custodian
     */
    function setCustodian
    (uint256 vorlex_id, address new_custodian) 
    external onlyAdmin {
        require(vorlexes[vorlex_id].custodian != address(0), "VORLEX_DOESNT_EXIST");
        vorlexes[vorlex_id].custodian = new_custodian;
        emit CustodianChanged(vorlex_id, new_custodian);
    }

    /**
        * @dev Get Total Number of Minted Vorlexes
     */
    function totalSupply() external view returns (uint256) {
        return _vorlex_count.current();
    }

    /**
        * @dev Create a Sale or Auction :: Only by Owner of Vorlex
        * @dev Tokens are transferred to contract when called
        * @param sale_id keccak256 hash of {vorlex_id, sale_not_auc, amount, seller, timestamp}
        * @param buy_not_bid True for BuySale Setup, False for BidSale Setup
        * @param is_escrow Indicates if it is an Escrow sale or not. Escrow sales are finalized by admin. All Bidding Auctions are Escrowed
        * @param vorlex_id Vorlex id
        * @param amount Amount of tokens to setup for Sale or Auction
        * @param worth For Sale => cost per tokem :: For Auction => cost for all tokens
        * @param bid_span Length of Auction :: For Sale => 1hr and doesn't count :: For Auction => 1 hour min & 72 hour max after first bid
        
     */
    function createSale(
        bytes32 sale_id, bool buy_not_bid, bool is_escrow, 
        uint256 vorlex_id, uint256 amount, uint256 worth, uint256 bid_span
    ) external {
        require(sales[sale_id].vorlex_id == 0, "DUPLICATE_SALE" );
        require(bid_span >= 1 hours && bid_span <= 3 days, "INVALID_BID_SPAN"); // * Check if the Bid Span is Valid
        require((buy_not_bid ? worth : worth/amount) >= MIN_SALE, "LOW_COST_PER_TOKEN");
        sale_status new_status = buy_not_bid && !is_escrow ? sale_status.OPEN_BUY : 
                                 buy_not_bid && is_escrow ? sale_status.OPEN_ESCROW_BUY : 
                                 sale_status.OPEN_BID;
        sales[sale_id] = Sale(new_status, vorlex_id, amount, 0,0, worth, bid_span, 0, msg.sender, address(0));
        safeTransferFrom(msg.sender, address(this), vorlex_id, amount, "");
        emit SaleSetup(sale_id, vorlex_id, amount, worth, bid_span, new_status);
    } 

    /**
        * @dev Purchase tokens that are on sale :: Will not call if Auction
        * @dev Tpkens are tramsferred to buyer 
        * @dev Custodian => Paid 10% :: Seller => Paid 80%
        * @param sale_id Auction ID of Sale
        * @param amount Amount of tokens to buy
        * @param buyer Address of token buyer
        * @param on_chain False if token was purchased with cash or other crypto on vorlex.co
        * @param metadata Off-chain purchase information
     */
    function purchaseToken(
        bytes32 sale_id, uint256 amount,address buyer, 
        bool on_chain, string memory metadata
    ) external payable {
        require (sales[sale_id].status == sale_status.OPEN_BUY || 
                 sales[sale_id].status == sale_status.OPEN_ESCROW_BUY, 
                 "NOT_OPEN_BUY_SALE");
        require(sales[sale_id].amount >= amount, "LOW_TOKENS_AVAILABLE");
        if(on_chain){   
            require(msg.value/amount == sales[sale_id].worth, "INVALID_TOKEN_PRICE");
        }
        else {
            require(hasRole(ADMIN_ROLE, msg.sender), "NOT_ADMIN");
        }
        this.safeTransferFrom( address(this), buyer, sales[sale_id].vorlex_id, amount, "");
        sales[sale_id].purchased_amount += amount;
        sales[sale_id].amount -= amount;
        emit Purchased(sale_id, amount, buyer, on_chain, metadata);
        if(sales[sale_id].status == sale_status.OPEN_BUY){     // Transfer Payment and Token if not Escrow
            uint256 _payment = msg.value;
            payable(vorlexes[sales[sale_id].vorlex_id].custodian).transfer(_payment /10);   // Pay Royalty to Custodian
            payable(sales[sale_id].seller).transfer(_payment * 4/5);       // Pay Seller 80% of Purchase
        }
    }

    /**
        * @dev Place a bid on Auction :: Will not call if for sale or Auction expired
        * @dev First bid sets expiry to time_of_bid + bid_span
        * @dev Bid should be at least 1% increment to highest bid
        * @param sale_id Auction ID
     */
    function placeBid(bytes32 sale_id) external payable {
        require (sales[sale_id].status == sale_status.OPEN_BID || 
                 sales[sale_id].status == sale_status.ACTIVE_BID, 
                 "NOT_VALID_BID_SALE");
        require ((msg.value - sales[sale_id].worth) >= (sales[sale_id].worth /100), "LOW_BID_INCREMENT" );
        require(sales[sale_id].expiry > block.timestamp || sales[sale_id].expiry == 0, "EXPIRED" );
        sales[sale_id].highest_bidder = msg.sender;
        sales[sale_id].worth = msg.value;
        emit Bidded(sale_id, msg.value);
        if(sales[sale_id].expiry == 0) {
            sales[sale_id].expiry = block.timestamp + sales[sale_id].bid_span;
            sales[sale_id].status = sale_status.ACTIVE_BID;
        }
        else {
            payable(sales[sale_id].highest_bidder).transfer(sales[sale_id].worth);
        }
    }

    /**
        * @dev Cancel a Sale/Unbidded Auction 
        * @dev Tokens are transferred back to seller
        * @param sale_id Auction or Sale ID
     */
    function cancelSale(bytes32 sale_id) external {
        require(msg.sender == sales[sale_id].seller || 
                hasRole(ADMIN_ROLE, msg.sender), 
                "NOT_SELLER_OR_ADMIN");
        require(sales[sale_id].status == sale_status.OPEN_BUY || 
                sales[sale_id].status == sale_status.OPEN_ESCROW_BUY || 
                sales[sale_id].status == sale_status.OPEN_BID , 
                "NOT_OPEN_SALE");
        if(msg.sender == sales[sale_id].seller){
            this.safeTransferFrom( address(this),  msg.sender,  sales[sale_id].vorlex_id, sales[sale_id].amount, "");
        }
        sales[sale_id].status = sales[sale_id].status == sale_status.OPEN_BUY ? sale_status.CANC_BUY :
                                sales[sale_id].status == sale_status.OPEN_BID ? sale_status.CANC_BID :
                                sale_status.CANC_ESCROW_BUY ;
        emit SaleCancelled(sale_id, msg.sender == sales[sale_id].seller);
    }

    /**
        * @dev Finalize Escrow :: Only by ADMIN
        * @param sale_id Auction ID of ESCROW PURCHASE or 
        * @param settle_amount Amount to Settle for ESCROW
    */
    function closeSale(bytes32 sale_id, uint256 settle_amount) external payable onlyAdmin {
        require(sales[sale_id].status == sale_status.OPEN_ESCROW_BUY ||
                sales[sale_id].status == sale_status.CANC_ESCROW_BUY ||
                sales[sale_id].status == sale_status.ACTIVE_BID , 
                "INVALID_CLOSE");
                
        emit SaleClosed(sale_id, settle_amount);
        if(sales[sale_id].status == sale_status.ACTIVE_BID){
            require(sales[sale_id].expiry < block.timestamp , "BIDDING_IS_OPEN" );
            safeTransferFrom( 
                address(this),  sales[sale_id].highest_bidder,  
                sales[sale_id].vorlex_id, sales[sale_id].amount, "");
            sales[sale_id].status = sale_status.CLOSED_BID;
            payable(vorlexes[sales[sale_id].vorlex_id].custodian).transfer(sales[sale_id].worth/10); // Custodian Royalty
            payable(sales[sale_id].seller).transfer(sales[sale_id].worth * 4/5);// Owner's Payout
        } else {
            require(sales[sale_id].purchased_amount - sales[sale_id].settled_amount >= 
                settle_amount , "INVALID SETTLE_AMOUNT" );
            uint256 payout = sales[sale_id].worth * settle_amount;
            sales[sale_id].settled_amount += settle_amount;
            payable(vorlexes[sales[sale_id].vorlex_id].custodian).transfer(payout/10); // Custodian Royalty
            payable(sales[sale_id].seller).transfer(payout * 4/5);// Owner's Payout
        }
    }

    /**
        * @dev Batch Move for SWAP, TRANSFER & BURN OPERATIONS
        * @dev Transfer is impossible between Vorlexers and Vorlexers cannot burn their tokens.
        * @param is_swap If operation is a Swap Operation
        * @param metadata More information about the Transfer Ops
    */

    function batchMove(
        uint256[] memory vorlex_ids, uint256[] memory amounts, address from, address to, 
        bool is_swap, string memory metadata 
    ) external {
        safeBatchTransferFrom(from, to, vorlex_ids, amounts, "");
        if(to == address(0)){ // If operation is a burn operation
            for (uint256 i = 0; i < amounts.length; i++) {
                vorlexes[vorlex_ids[i]].amount -= amounts[i];
            }
            emit Burnt(metadata);
        }
        if(is_swap){
            emit Swapped(metadata);
        }
        emit BatchMoved(metadata);
    }

    /**
        * @dev Vorlexes can only be transferred to this Contract/Admin or From this Contract/Admin by NON-ADMIN
        * @dev ADMIN can initiate any transfers
     */
    function _beforeTokenTransfer(
        address operator,address from,address to,
        uint256[] memory ids,uint256[] memory amounts, bytes memory data
    )
        internal virtual override(ERC1155)
    {
        super._beforeTokenTransfer(operator, from, to, ids, amounts, data);
        require (hasRole(ADMIN_ROLE, from) || hasRole(ADMIN_ROLE, to) || hasRole(ADMIN_ROLE, operator), "INVALID_TOKEN_TRANSFER");

    }

    function supportsInterface(bytes4 interfaceId) 
    public view virtual override(AccessControl, ERC1155, ERC1155Receiver) 
    returns (bool) {
        return super.supportsInterface(interfaceId);
    }
    
}
// https://vorlex.co Contract For Vorlex Crypto Marketplace 
// Vorlex Contract written by Kaptain Ti 
// Vorlex Platform Owned by Franklin Ndekwe and Kaptain Ti(nee C.C. Thompson)
function placeBid(bytes32 sale_id) external payable {
    require (sales[sale_id].status == sale_status.OPEN_BID || 
             sales[sale_id].status == sale_status.ACTIVE_BID, 
             "NOT_VALID_BID_SALE");
    require ((msg.value - sales[sale_id].worth) >= (sales[sale_id].worth /100), "LOW_BID_INCREMENT" );
    require(sales[sale_id].expiry > block.timestamp || sales[sale_id].expiry == 0, "EXPIRED" );
    sales[sale_id].highest_bidder = msg.sender;
    sales[sale_id].worth = msg.value;
    emit Bidded(sale_id, msg.value);
    if(sales[sale_id].expiry == 0) {
        sales[sale_id].expiry = block.timestamp + sales[sale_id].bid_span;
        sales[sale_id].status = sale_status.ACTIVE_BID;
    }
    else {
        payable(sales[sale_id].highest_bidder).transfer(sales[sale_id].worth);
    }
}

/**
 * @dev Place a bid on Auction :: Will not call if for sale or Auction expired
 * @dev First bid sets expiry to time_of_bid + bid_span
 * @dev Bid should be at least 1% increment to highest bid
 * @param sale_id Auction ID
 */