contract AtomicSwap is IERC721Receiver, Ownable, Pausable {
    // Importo i contatori
    using Counters for Counters.Counter;
    // Costanti per gestire il tempo in formato unix timestamp
    uint256 constant secs = 86400;

    // Dichiaro contatori
    Counters.Counter private _swapIds;

    // Flag per gestire la create swap
    bool private swapFlag;
    
    // Stato dello swap
    enum swapStatus { Opened, Closed, Cancelled }
    
    // Rappresentazione dello swap
    struct swapIntent {
        Counters.Counter id;
        address nftOwnerOne;
        address dappAddressOne;
        uint256 nftTokenIdOne;
        address nftOwnerTwo;
        address dappAddressTwo;
        uint256 nftTokenIdTwo;
        uint256 swapStart;
        uint256 swapEnd;
        swapStatus status;
    }
    
    struct paymentStruct {
        bool status;
        uint256 value;
    }
    
    // Mapping chiave/valore per ottenere le informazioni sullo swap
    mapping (address => swapIntent[]) swapList;
    mapping (address => Counters.Counter) swapCount;
    
    address public proxyAddress;
    AtomicProxyInterface proxyInterface;
    ERC721Interface erc721Interface;
    paymentStruct payment;
    
    
    // Events
    event swapEvent(address indexed _swapCreator, uint256 indexed time, swapStatus indexed _status, uint256 _swapId, address _swapCounterPart);
    event paymentReceived(address indexed _payer, uint256 _value);
    
    constructor() public {
        payment.status=false;
        payment.value=0;
    }

    //Questa chiamata ogni qual volta vengono inviati degli ethereum con il calldata empty
    receive() external payable { 
        emit paymentReceived(msg.sender, msg.value);
    }
    
    // Creo lo swap
    function createSwapIntent(address _dappAddressOne, uint256 _tokenIdOne, address _dappAddressTwo, uint256 _tokenIdTwo) payable public isPaused {
        swapFlag = false;
        if((_dappAddressOne == _dappAddressTwo) && (_tokenIdOne == _tokenIdTwo))
            swapFlag = true;
            require(swapFlag==false, "Impossible to swap the same item");
        if(payment.status)
            require(msg.value>=payment.value, "Not enought WEI for handle the transaction");
        proxyInterface.checkAddresses(_dappAddressOne, _dappAddressTwo);
        _swapIds = swapCount[msg.sender];
        swapList[msg.sender].push(swapIntent(_swapIds, msg.sender, _dappAddressOne, _tokenIdOne, address(0), _dappAddressTwo, _tokenIdTwo, block.timestamp, 0, swapStatus.Opened));
        proxyInterface.safeTransferFrom(_dappAddressOne, msg.sender, address(this), _tokenIdOne);
        emit swapEvent(msg.sender, (block.timestamp-(block.timestamp%secs)), swapStatus.Opened, _swapIds.current(), address(0));
        emit paymentReceived(msg.sender, msg.value);
        _swapIds.increment();
        swapCount[msg.sender] = _swapIds;
    }
    
    // Concludo lo swap
    function closeSwapIntent(address _swapCreator, uint256 _swapId) payable public isPaused {
        require(swapList[_swapCreator][_swapId].status == swapStatus.Opened, "Swap Status is not opened");
        if(payment.status)
            require(msg.value>=payment.value, "Not enought WEI for handle the transaction");
        swapList[_swapCreator][_swapId].nftOwnerTwo = msg.sender;
        swapList[_swapCreator][_swapId].swapEnd = block.timestamp;
        swapList[_swapCreator][_swapId].status = swapStatus.Closed;
        proxyInterface.safeTransferFrom(swapList[_swapCreator][_swapId].dappAddressTwo, msg.sender, swapList[_swapCreator][_swapId].nftOwnerOne, swapList[_swapCreator][_swapId].nftTokenIdTwo);
        erc721Interface = ERC721Interface(swapList[_swapCreator][_swapId].dappAddressOne);
        erc721Interface.approve(proxyAddress, swapList[_swapCreator][_swapId].nftTokenIdOne);
        proxyInterface.safeTransferFrom(swapList[_swapCreator][_swapId].dappAddressOne, address(this), swapList[_swapCreator][_swapId].nftOwnerTwo, swapList[_swapCreator][_swapId].nftTokenIdOne);
        emit swapEvent(msg.sender, (block.timestamp-(block.timestamp%secs)), swapStatus.Closed, _swapId, _swapCreator);
        emit paymentReceived(msg.sender, msg.value);
    }

    // Cancello lo swap
    function cancelSwapIntent(uint256 _swapId) public {
        require(swapList[msg.sender][_swapId].status == swapStatus.Opened, "Swap Status is not opened");
        erc721Interface = ERC721Interface(swapList[msg.sender][_swapId].dappAddressOne);
        erc721Interface.approve(proxyAddress, swapList[msg.sender][_swapId].nftTokenIdOne);
        swapList[msg.sender][_swapId].swapEnd = block.timestamp;
        swapList[msg.sender][_swapId].status = swapStatus.Cancelled;
        proxyInterface.safeTransferFrom(swapList[msg.sender][_swapId].dappAddressOne, address(this), msg.sender, swapList[msg.sender][_swapId].nftTokenIdOne);
        emit swapEvent(msg.sender, (block.timestamp-(block.timestamp%secs)), swapStatus.Cancelled, _swapId, address(0));
    }
    
    //FIXME: SERVE? Si attiva quando viene utilizzata la SafeTransfer
    function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external override returns (bytes4) {
        return bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
    }
    
    // Abbino l'indirizzo all'interfaccia del proxy
    function setProxyAddress(address _proxyAddress) public onlyOwner isPaused {
        proxyAddress = _proxyAddress;
        proxyInterface = AtomicProxyInterface(_proxyAddress);
    }
    
    // Setto il pagamento
    function setPayment(bool _status, uint256 _value) public onlyOwner isPaused {
        payment.status = _status;
        payment.value = _value;
    }
    
    // Ottengo l'importo di WEI settato
    function getWeiPayValueAmount() public view returns(uint256) {
        return payment.value;
    }
    
    // Ottengo il numero di swap legati a questo indirizzo
    function getSwapCount() public view returns(uint256) {
        return swapCount[msg.sender].current();
    }
    
    // Ottengo lo swap indicato
    function getSwapIntentByAddress(address _creator, uint256 _index) public view returns(swapIntent memory) {
        return swapList[_creator][_index];
    }

    // Ottengo gli swap dell'owner passando l'id
    function getSwapIntentByOwner(uint256 _index) public view returns(swapIntent memory) {
        return swapList[msg.sender][_index];
    }

    // Ottengo il balance dello smart contract
    function getVaultBalance() public view onlyOwner isPaused returns(uint256) {
        return address(this).balance;
    }

    // Sposto il balance dello smart contract
    function sendVaultBalance(uint256 _amount, address payable _receiver) public onlyOwner isPaused {
        require(address(this).balance>= _amount, "Not enought WEI in the balance");
        _receiver.transfer(_amount);
    }
}
function setProxyAddress(address _proxyAddress) public onlyOwner isPaused {
        proxyAddress = _proxyAddress;
        proxyInterface = AtomicProxyInterface(_proxyAddress);
    }
// Abbino l'indirizzo all'interfaccia del proxy