// File: /contracts/TicketsHelper.sol



// SPDX-License-Identifier: GPL-3.0
// solhint-disable-next-line
pragma solidity 0.8.12;
import "@openzeppelin/contracts/access/Ownable.sol";
import "./interface/IBapTickets.sol";

/// @title Bulls and Apes Project - Tickets Helper
/// @author BAP Dev Team
/// @notice Helper contract to interact with BAP Tickets contract
contract TicketsHelper is Ownable {
    /// @notice Contract address for BAP Tickets
    IBapTickets public bapTickets;

    /// @notice signer address
    address public secret;

    /// @notice Mapping for addresses allowed to use this contract
    mapping(address => bool) public isAuthorized;
    /// @notice Mapping for used signatures
    mapping(bytes => bool) public isSignatureUsed;
    /// @notice Mapping for users, event id and amount of tickets purchased
    mapping(address => mapping(uint256 => uint256)) public ticketsPurchased;

    /// @notice Deploys the contract
    /// @param _bapTickets BAP Tickets contract address

    constructor(address _bapTickets, address _secret) {
        bapTickets = IBapTickets(_bapTickets);
        secret = _secret;

        isAuthorized[msg.sender] = true;
    }

    modifier onlyAuthorized() {
        require(isAuthorized[msg.sender], "Not Authorized");
        _;
    }

    /// @notice fallback function
    receive() external payable {}

    /// @notice Call createEvent function on BAP Tickets contract
    /// @param _maxSupply Maximum supply of tickets
    function createEvent(uint256 _maxSupply) external onlyAuthorized {
        bapTickets.createEvent(_maxSupply, 0, 0, 0, 0);
    }

    /// @notice Call airdrop function on BAP Tickets contract
    /// @param eventId Event Id
    /// @param account Address to send the tickets
    /// @param amount Quantity of tickets to mint
    function airdrop(
        uint256 eventId,
        address account,
        uint256 amount
    ) external onlyAuthorized {
        bapTickets.airdrop(eventId, account, amount);
    }

    ///@notice Purchase tickets for an event
    ///@param eventId Event Id
    ///@param amount Quantity of tickets to purchase
    ///@param price Price of the tickets
    ///@param timeOut Time out for the purchase
    ///@param maxPerWallet Maximum amount of tickets per wallet
    ///@param recipient Address to receive the tickets
    ///@param signature Signature to verify the purchase
    function purchaseTickets(
        uint256 eventId,
        uint256 amount,
        uint256 price,
        uint256 timeOut,
        uint256 maxPerWallet,
        address recipient,
        bytes memory signature
    ) external payable {
        require(
            !isSignatureUsed[signature],
            "purchaseTickets: Signature already used"
        );
        require(
            ticketsPurchased[recipient][eventId] + amount <= maxPerWallet,
            "purchaseTickets: Max per wallet reached"
        );
        require(block.timestamp <= timeOut, "purchaseTickets: Time out");
        require(msg.value >= price, "purchaseTickets: Invalid ETH amount");
        require(
            _verifyHashSignature(
                keccak256(
                    abi.encode(eventId, amount, price, timeOut, maxPerWallet, recipient)
                ),
                signature
            ),
            "purchaseTickets: Invalid signature"
        );

        isSignatureUsed[signature] = true;

        ticketsPurchased[recipient][eventId] += amount;

        if(msg.value > price){
            (bool success, ) = recipient.call{value: msg.value - price}("");
            require(success, "purchaseTickets: Unable to send refund eth");
        }

        bapTickets.airdrop(eventId, recipient, amount);
    }

    // Ownable

    /// @notice authorise a new address to use this contract
    /// @param operator Address to be set
    /// @param status Can use this contract or not
    /// @dev Only contract owner can call this function
    function setAuthorized(address operator, bool status) external onlyOwner {
        isAuthorized[operator] = status;
    }

    /// @notice Change the address for signer
    /// @param _newAddress New address to be set
    /// @dev Can only be called by the contract owner
    function setSecret(address _newAddress) external onlyOwner {
        secret = _newAddress;
    }

    /// @notice Transfer ownership from external contracts owned by this contract
    /// @param _contract Address of the external contract
    /// @param _newOwner New owner
    /// @dev Only contract owner can call this function
    function transferOwnershipExternalContract(
        address _contract,
        address _newOwner
    ) external onlyOwner {
        Ownable(_contract).transferOwnership(_newOwner);
    }

    /// @notice Change the address for Tickets Contract
    /// @param _newAddress New address to be set
    /// @dev Can only be called by the contract owner
    function setTicketsContract(address _newAddress) external onlyOwner {
        bapTickets = IBapTickets(_newAddress);
    }    

    /// @notice Call withdrawETH function on BAP Tickets contract
    /// @param _address Address to send the ETH
    /// @param amount Quantity of ETH to send
    function withdrawETH(
        address _address,
        uint256 amount
    ) external onlyOwner {
        bapTickets.withdrawETH(_address, amount);
    }

    function withdrawRemainingETH(address _address, uint256 amount)
        external
        onlyOwner
    {
        require(amount <= address(this).balance, "Insufficient funds");
        (bool success, ) = _address.call{value: amount}("");

        require(success, "Unable to send eth");
    }

    function _verifyHashSignature(
        bytes32 freshHash,
        bytes memory signature
    ) internal view returns (bool) {
        bytes32 hash = keccak256(
            abi.encodePacked("\x19Ethereum Signed Message:\n32", freshHash)
        );

        bytes32 r;
        bytes32 s;
        uint8 v;

        if (signature.length != 65) {
            return false;
        }
        assembly {
            r := mload(add(signature, 32))
            s := mload(add(signature, 64))
            v := byte(0, mload(add(signature, 96)))
        }

        if (v < 27) {
            v += 27;
        }

        address signer = address(0);
        if (v == 27 || v == 28) {
            // solium-disable-next-line arg-overflow
            signer = ecrecover(hash, v, r, s);
        }
        return secret == signer;
    }
}

// File: /contracts/interface/IBapTickets.sol



// SPDX-License-Identifier: GPL-3.0
// solhint-disable-next-line
pragma solidity 0.8.12;

interface IBapTickets {
    function withdrawETH(address _address, uint256 amount) external;

    function airdrop(uint256 eventId, address account, uint256 amount) external;

    function createEvent(
        uint256 _maxSupply,
        uint8 _maxPerTx,
        uint8 _maxPerWallet,
        uint256 _ticketPrice,
        uint8 _status // 0: sale close, 1: public sale - everybody can buy, no checks, 2: only gods owner - only god owners can buy (you're currently checking), 3: only signature - only users with a valid signature
    ) external;
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}