contract Nft is ERC721Full { 
    struct data { string URL;address payable creator;uint256 Token_ID;uint256 Token_Price;bool Status; }
    address payable public manager; 
      data[] public nft;
      mapping(string => bool) _nftExists;
    
      constructor() ERC721Full("Colexion", "CLXN") public { manager = address(0x7812ca983E0be569FEc1863Baaec388479577B17); }
      
    function mint(string memory url, uint64 Token_Price, bool _status) public {
        require(!_nftExists[url], "AlreadyExist.");
        data memory data123 = data(url,msg.sender,nft.length,Token_Price,_status);
        uint _id = nft.push(data123);
        _mint(msg.sender, _id-1);
        _setTokenURI( _id-1, url);
        _nftExists[url] = true;
        approve(address(this), _id-1);
    }
     
    function changeStatus(uint Token_ID) public only_owner(Token_ID) not_in_auction(Token_ID) {
        nft[Token_ID].Status = !nft[Token_ID].Status;
    }
    
    function purchaseToken(uint64 t_id, uint8 m, uint8 r, uint8 o) public payable not_in_auction(t_id){
        require(nft[t_id].Status == true, "TokenNotForSale");
         require(nft[t_id].Token_Price <= msg.value, "Invalid price");
        t_transfer(ownerOf(t_id), msg.sender, t_id, msg.value,m,r,o);
    }
     
    function t_transfer(address owner, address to, uint64 t_id, uint256 t_price,uint8 m, uint8 r, uint8 o)internal{
        address payable OwnToken = address (uint168(owner));
        manager.transfer( ( (t_price / 100) * m ) );   // transfer 7% to token manager.
        (nft[t_id].creator).transfer( ( (t_price / 100) * r ) );   // transfer 10% to token creator.
        (OwnToken).transfer( ( (t_price / 100) * o ) );   // transfer 83% to token owner.
        transferFrom(owner, to, t_id);
        nft[t_id].Status = false;
        nft[t_id].Token_Price = t_price;
    }
     
    function updateTokenPrice(uint t_id, uint _price) public only_owner(t_id) not_in_auction(t_id) returns(string memory){
        nft[t_id].Token_Price = _price;
        return "PriceUpdated";
     }
     
    mapping (uint => bool) public T_In_Auction;
    mapping (uint => Auction_data) public T_Auction;
    mapping(uint => mapping(address => uint)) public bids;
    mapping(uint => address[]) public token_bidders;    
    address[] bidder_list;

    struct Auction_data{
        uint Token_id; 
        address auction_owner; 
        address auction_winner; 
        uint256 auction_win_bid; 
        uint256 auction_start; 
        uint256 auction_end; 
        uint256 highestBid; 
        address highestBidder;
        uint256 minimunBid; 
        uint256 maximunBid; 
        bool state;
        address[] bidders;
    }

    function create_Auction (uint _biddingTime, uint t_id, uint _minBid, uint _maxBid) only_owner(t_id) not_in_auction(t_id) public {
        require(nft[t_id].Status == false, "TokeForSale");
        address[] memory emptyAddressList;
        Auction_data memory data1750 = Auction_data(t_id,ownerOf(t_id),address(0),0, now,(now +_biddingTime*1 minutes),0,address(0),_minBid,_maxBid, true, emptyAddressList);
        T_Auction[t_id] = data1750; 
        T_In_Auction[t_id] = true; 
        token_bidders[t_id].length = 0;
        approve(address(this), t_id);
    }

    // Not done yet => agr koi higgest bid kerre to token usko transfer hojai
    function bid(uint64 T_id) public payable in_auction(T_id) only_bidders(T_id) not_auction_winner(T_id) returns (bool){ 
        require(bids[T_id][msg.sender] + msg.value >=  T_Auction[T_id].minimunBid, "makeHigherBid");
        require(bids[T_id][msg.sender] + msg.value <=  T_Auction[T_id].maximunBid, "makeLowerBid");
        if(block.timestamp < T_Auction[T_id].auction_end){
            if(bids[T_id][msg.sender] + msg.value >= T_Auction[T_id].maximunBid){
                // bid hits maximum amount
                T_Auction[T_id].state = false;
                T_Auction[T_id].auction_win_bid = bids[T_id][msg.sender] + msg.value;
                T_Auction[T_id].auction_winner = msg.sender;
                T_Auction[T_id].highestBidder = address(0);
                T_Auction[T_id].highestBid = 0;
                bids[T_id][msg.sender] = 0;
                T_In_Auction[T_id] = false; 
                t_transfer(ownerOf(T_id), msg.sender, T_id, T_Auction[T_id].auction_win_bid,7,10,83);
                emit EndedEvent("Auction ended", block.timestamp); 
            } else {
                // bid not hits maximum amount
                bids[T_id][msg.sender] = bids[T_id][msg.sender] + msg.value;
                bidder_list = token_bidders[T_id];
                bidder_list.push(msg.sender);
                token_bidders[T_id] = bidder_list;
                T_Auction[T_id].highestBidder = T_Auction[T_id].highestBid > bids[T_id][msg.sender]  ? T_Auction[T_id].highestBidder : msg.sender;
                T_Auction[T_id].highestBid =  T_Auction[T_id].highestBid > bids[T_id][msg.sender] ? T_Auction[T_id].highestBid : bids[T_id][msg.sender];
                T_Auction[T_id].bidders.push(msg.sender);
                return true;
            }
        } else {
            end_auction_auto(T_id);
        }
    } 
    
    function withdraw(uint T_id) public payable only_bidders(T_id) auction_ended(T_id) not_auction_winner(T_id) returns (bool){
        uint amount = bids[T_id][msg.sender];
        require( amount > 0, "balance 0");
        
        msg.sender.transfer(amount); 
        bids[T_id][msg.sender] = 0; 
        emit WithdrawalEvent(msg.sender, bids[T_id][msg.sender]); 
        return true;
    }  
    
    function end_auction(uint64 T_id) public payable only_owner(T_id) returns (bool, string memory) { 
        require(T_In_Auction[T_id] == true, "TokenInAuction");
        require(nft[T_id].Status == false,"TokenForSale");
        T_Auction[T_id].state = false;
        T_In_Auction[T_id] = false; 
        if(T_Auction[T_id].highestBidder == address(0)){ return (true, "Auction Ended");}
        T_Auction[T_id].auction_win_bid = T_Auction[T_id].highestBid;
        T_Auction[T_id].auction_winner = T_Auction[T_id].highestBidder;
        T_Auction[T_id].highestBidder = address(0);
        T_Auction[T_id].highestBid = 0;
        bids[T_id][T_Auction[T_id].auction_winner] = 0;
        t_transfer(ownerOf(T_id), T_Auction[T_id].auction_winner, T_id, T_Auction[T_id].auction_win_bid,7,10,83);
        emit EndedEvent("Auction ended", block.timestamp); 
        return (true, "Auction Ended");
    }
    
    function end_auction_auto(uint64 T_id)public payable auctionTime_ends(T_id) returns (bool, string memory) { 
        require(T_In_Auction[T_id] == true , "This Token is not in Auction");
        T_Auction[T_id].state = false;
        T_In_Auction[T_id] = false; 
        T_Auction[T_id].auction_win_bid = T_Auction[T_id].highestBid;
        T_Auction[T_id].auction_winner = T_Auction[T_id].highestBidder;
        T_Auction[T_id].highestBidder = address(0);
        T_Auction[T_id].highestBid = 0;
         bids[T_id][T_Auction[T_id].auction_winner] = 0;
        msg.sender.transfer(msg.value);
        // address(uint168(ownerOf(T_id))).transfer(T_Auction[T_id].highestBid);
        t_transfer(ownerOf(T_id), T_Auction[T_id].auction_winner, T_id, T_Auction[T_id].auction_win_bid,7,10,83);
        emit EndedEvent("Auction ended", block.timestamp); 
        return (true, "Auction Ended");
    }
     
    modifier in_auction(uint T_id){ 
        require( T_In_Auction[T_id] == true, "tokenNotInAuction");
        _;
    } 
    modifier not_in_auction(uint T_id){ 
        require(T_In_Auction[T_id] == false , "TokenInAuction");
        _;
    } 
   
    modifier an_ongoing_auction(uint T_id){ 
        require( block.timestamp < T_Auction[T_id].auction_end, "BiddingIsEnd");
        _;
    } 
   
    modifier auctionTime_ends(uint T_id){ 
        require( block.timestamp > T_Auction[T_id].auction_end, "BiddingNotEnd");
        _;
    } 
   
    modifier auction_ended(uint T_id){ 
        require( T_Auction[T_id].state == false, "AuctionIsGoing");
        _;
    } 
   
    modifier only_owner(uint T_id){ 
        require(msg.sender == ownerOf(T_id), "OnlyOwner");
        _;
    } 
   
    modifier only_bidders(uint T_id){ 
        require(msg.sender != T_Auction[T_id].auction_owner, "OnlyBidderAction");
        _;
    } 
    
    modifier not_auction_winner(uint T_id){ 
        require(msg.sender != T_Auction[T_id].auction_winner, "AuctionWinner");
        _;
    } 
    event BidEvent(address indexed Bidder, uint256 Bid); 
    event WithdrawalEvent(address withdrawer, uint256 amount);
    event EndedEvent(string message, uint256 time);

}
function bid(uint64 T_id) public payable in_auction(T_id) only_bidders(T_id) not_auction_winner(T_id) returns (bool){ 
    require(bids[T_id][msg.sender] + msg.value >=  T_Auction[T_id].minimunBid, "makeHigherBid");
    require(bids[T_id][msg.sender] + msg.value <=  T_Auction[T_id].maximunBid, "makeLowerBid");
    if(block.timestamp < T_Auction[T_id].auction_end){
        if(bids[T_id][msg.sender] + msg.value >= T_Auction[T_id].maximunBid){
            // bid hits maximum amount
            T_Auction[T_id].state = false;
            T_Auction[T_id].auction_win_bid = bids[T_id][msg.sender] + msg.value;
            T_Auction[T_id].auction_winner = msg.sender;
            T_Auction[T_id].highestBidder = address(0);
            T_Auction[T_id].highestBid = 0;
            bids[T_id][msg.sender] = 0;
            T_In_Auction[T_id] = false; 
            t_transfer(ownerOf(T_id), msg.sender, T_id, T_Auction[T_id].auction_win_bid,7,10,83);
            emit EndedEvent("Auction ended", block.timestamp); 
        } else {
            // bid not hits maximum amount
            bids[T_id][msg.sender] = bids[T_id][msg.sender] + msg.value;
            bidder_list = token_bidders[T_id];
            bidder_list.push(msg.sender);
            token_bidders[T_id] = bidder_list;
            T_Auction[T_id].highestBidder = T_Auction[T_id].highestBid > bids[T_id][msg.sender]  ? T_Auction[T_id].highestBidder : msg.sender;
            T_Auction[T_id].highestBid =  T_Auction[T_id].highestBid > bids[T_id][msg.sender] ? T_Auction[T_id].highestBid : bids[T_id][msg.sender];
            T_Auction[T_id].bidders.push(msg.sender);
            return true;
        }
    } else {
        end_auction_auto(T_id);
    }
} 

// Not done yet => agr koi higgest bid kerre to token usko transfer hojai