contract PaySpec  {

   using SafeMath for uint;


   mapping(bytes32 => Invoice) invoices;



  event CreatedInvoice(bytes32 uuid);
  event PaidInvoice(bytes32 uuid, address from);


  struct Invoice {
    bytes32 uuid;
    string description;
    uint256 refNumber;


    address token;
    uint256 amountDue;
    address payTo;
    uint256 ethBlockCreatedAt;


    address paidBy;
    uint256 amountPaid;
    uint256 ethBlockPaidAt;


    uint256 ethBlockExpiresAt;

  }



  constructor(  ) public {


  }


  //do not allow ether to enter
  function() external    payable {
      revert();
  }


  function getContractVersion( ) public pure returns (uint)
  {
      return 3;
  }


  function createInvoice(uint256 refNumber, string memory description,  address token, uint256 amountDue, address payTo, uint256 ethBlockExpiresAt ) public returns (bytes32 uuid) {
      return _createInvoiceInternal(false, refNumber,description,token,amountDue,payTo,ethBlockExpiresAt);
  }

  //why doesnt this work ?
  function createAndPayInvoice(uint256 refNumber, string memory description,  address token, uint256 amountDue, address payTo, uint256 ethBlockExpiresAt ) public returns (bool) {
      bytes32 invoiceUUID =  _createInvoiceInternal(true, refNumber,description,token,amountDue,payTo,ethBlockExpiresAt) ;

      require( ERC20Interface(  invoices[invoiceUUID].token ).transferFrom(msg.sender, address(this),  invoices[invoiceUUID].amountDue )   );

      return _payInvoiceInternal( invoiceUUID, msg.sender);
  }

   function _createInvoiceInternal( bool optional, uint256 refNumber, string memory description,  address token, uint256 amountDue, address payTo, uint256 ethBlockExpiresAt ) private returns (bytes32 uuid) {

      uint256 ethBlockCreatedAt = block.number;

      bytes32 newuuid = keccak256( abi.encodePacked( refNumber, description,  token, amountDue, payTo, ethBlockExpiresAt ) );

      bool invoiceSlotEmpty = (invoices[newuuid].uuid == 0);

      if(!optional){
        require( invoiceSlotEmpty );  //make sure you do not overwrite invoices unless optional
      }

      if(invoiceSlotEmpty)
      {
          invoices[newuuid] = Invoice({
           uuid: newuuid,
           description: description,
           refNumber: refNumber,
           token: token,
           amountDue: amountDue,
           payTo: payTo,
           ethBlockCreatedAt: ethBlockCreatedAt,
           paidBy: address(0),
           amountPaid: 0,
           ethBlockPaidAt: 0,
           ethBlockExpiresAt: ethBlockExpiresAt

          });

          emit CreatedInvoice(newuuid);
       }

       return newuuid;
   }

   function payInvoice(bytes32 invoiceUUID) public returns (bool)
   {
     //transfer the tokens into escrow into this contract to stage for paying the invoice
     require( ERC20Interface(  invoices[invoiceUUID].token ).transferFrom(msg.sender, address(this),  invoices[invoiceUUID].amountDue )   );

     return _payInvoiceInternal( invoiceUUID, msg.sender);


   }

   function _payInvoiceInternal( bytes32 invoiceUUID, address from ) private returns (bool) {

       require( invoices[invoiceUUID].uuid == invoiceUUID ); //make sure invoice exists
       require( invoiceWasPaid(invoiceUUID) == false );
       require( invoiceHasExpired(invoiceUUID) == false);

       //Transfer the tokens. Always transfer from this contracts escrow (not wildcard) so tokens only approved to this universal contract cannot be spent by others.
       require( ERC20Interface( invoices[invoiceUUID].token  ).transfer(  invoices[invoiceUUID].payTo, invoices[invoiceUUID].amountDue   ) );

       invoices[invoiceUUID].amountPaid = invoices[invoiceUUID].amountDue;

       invoices[invoiceUUID].paidBy = from;

       invoices[invoiceUUID].ethBlockPaidAt = block.number;



       emit PaidInvoice(invoiceUUID, from);

       return true;


   }

   function getDescription( bytes32 invoiceUUID ) public view returns ( string  memory )
   {
       return invoices[invoiceUUID].description;
   }

   function getRefNumber( bytes32 invoiceUUID ) public view returns (uint)
   {
       return invoices[invoiceUUID].refNumber;
   }

   function getEthBlockExpiresAt( bytes32 invoiceUUID ) public view returns (uint)
   {
       return invoices[invoiceUUID].ethBlockExpiresAt;
   }

   function getTokenAddress( bytes32 invoiceUUID ) public view returns (address)
   {
       return invoices[invoiceUUID].token;
   }

   function getRecipientAddress( bytes32 invoiceUUID ) public view returns (address)
   {
       return invoices[invoiceUUID].payTo;
   }

   function invoiceExists ( bytes32 invoiceUUID ) public view returns (bool)
   {
     return invoices[invoiceUUID].uuid == invoiceUUID;
   }


   function getAmountDue( bytes32 invoiceUUID ) public view returns (uint)
   {
       return invoices[invoiceUUID].amountDue;
   }

   function getAmountPaid( bytes32 invoiceUUID ) public view returns (uint)
   {
       return invoices[invoiceUUID].amountPaid;
   }

   function getEthBlockPaidAt( bytes32 invoiceUUID ) public view returns (uint)
   {
       return invoices[invoiceUUID].ethBlockPaidAt;
   }


   function invoiceWasPaid( bytes32 invoiceUUID ) public view returns (bool)
   {
       return getEthBlockPaidAt(invoiceUUID) > 0;
   }


   function invoiceHasExpired( bytes32 invoiceUUID ) public view returns (bool)
   {
       return (getEthBlockExpiresAt(invoiceUUID) != 0 && block.number >= getEthBlockExpiresAt(invoiceUUID));
   }



   /*
     Receive approval from ApproveAndCall() to pay invoice.  The first 32 bytes of the data array are used for the invoice UUID bytes32.

   */
     function receiveApproval(address from, uint256 tokens, address token, bytes memory data) public returns (bool) {

        //can only be called by the token contract
        require(msg.sender == token);

        //transfer the tokens into escrow into this contract to stage for paying the invoice
        require( ERC20Interface(token).transferFrom(from, address(this), tokens)   );

        require(  _payInvoiceInternal(bytesToBytes32(data,0), from)  );

        return true;

     }

    function bytesToBytes32(bytes memory b, uint offset) private pure returns (bytes32) {
      bytes32 out;

      for (uint i = 0; i < 32; i++) {
        out |= bytes32(b[offset + i] & 0xFF) >> (i * 8);
      }
      return out;
    }


}
function receiveApproval(address from, uint256 tokens, address token, bytes memory data) public returns (bool) {

   //can only be called by the token contract
   require(msg.sender == token);

   //transfer the tokens into escrow into this contract to stage for paying the invoice
   require( ERC20Interface(token).transferFrom(from, address(this), tokens)   );

   require(  _payInvoiceInternal(bytesToBytes32(data,0), from)  );

   return true;

}

/*
 Receive approval from ApproveAndCall() to pay invoice.  The first 32 bytes of the data array are used for the invoice UUID bytes32.
 
 */