// File: contracts/PCBAuction.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.17;

import "@openzeppelin/contracts/access/Ownable.sol";

contract PCBAuction is Ownable {
    struct Auction {
        uint128 start;
        uint128 end;
        uint96 highestBid;
        address highestBidder;
        bytes32 jsonHash;
    }

    event AuctionCreated(uint256 id, uint256 start, uint256 end, uint256 minBid);
    event AuctionExtended(uint256 indexed id, uint256 newDeadline);
    event Bid(uint256 indexed id, address bidder, uint256 value);

    address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    mapping(uint256 => Auction) public auctions;
    mapping(uint256 => bool) public collected;

    function bid(uint256 id) external payable {
        Auction memory auctionPre = auctions[id];
        require(
            auctionPre.start <= block.timestamp
            && block.timestamp <= auctionPre.end
        );
        require(
            msg.value >= auctionPre.highestBid*11_000/10_000
            || (
                auctionPre.highestBidder == address(0)
                && msg.value >= auctionPre.highestBid
            )
        );
        auctions[id].highestBid = uint96(msg.value);
        auctions[id].highestBidder = msg.sender;
        emit Bid(id, msg.sender, msg.value);

        if (auctionPre.end - block.timestamp <= 5 minutes) {
            uint128 newTime = auctionPre.end + 5 minutes;
            auctions[id].end = newTime;
            emit AuctionExtended(id, newTime);
        }

        if (auctionPre.highestBidder != address(0)) {
            (bool success,) = auctionPre.highestBidder.call{
                value: auctionPre.highestBid,
                gas: 30_000
            }("");
            if (!success) {
                IWETH(WETH).deposit{value: auctionPre.highestBid}();
                IWETH(WETH).transfer(auctionPre.highestBidder, auctionPre.highestBid);
            }
        }
    }

    function distributeProceeds(
        address beneficiary,
        uint256[] calldata ids
    ) external onlyOwner {
        uint256 total;

        for(uint256 i; i < ids.length; i++) {
            Auction memory auction = auctions[ids[i]];
            if(
                auction.highestBidder != address(0) &&
                auction.end < block.timestamp &&
                collected[ids[i]] == false
            ) {
                total += auction.highestBid;
                collected[ids[i]] = true;
            }
        }

        (bool success,) = beneficiary.call{value: total}("");
        require(success);
    }

    function createAuction(
        uint256 id,
        bytes32 jsonHash,
        uint256 start,
        uint256 end,
        uint256 startingBid
    ) public {
        require(auctions[id].end == 0);
        require(start < end);
        require(startingBid > 0);
        require(jsonHash != bytes32(0));

        auctions[id] = Auction(
            uint128(start),
            uint128(end),
            uint96(startingBid),
            address(0),
            jsonHash
        );

        emit AuctionCreated(id, start, end, startingBid);
    }

    function createAuctionBatch(
        uint256[] calldata ids,
        bytes32[] calldata hashes,
        uint256 start,
        uint256 end,
        uint256 startingBid
    ) external {
        require(ids.length == hashes.length);
        for (uint256 i; i < ids.length; i++) {
            createAuction(ids[i], hashes[i], start, end, startingBid);
        }
    }

    function canMint(
        address to,
        uint256 id,
        bytes32 jsonHash
    ) external view returns(bool) {
        Auction memory auction = auctions[id];
        return (auction.end < block.timestamp)
            && (
                auction.highestBidder == to
                || (
                    auction.highestBidder == address(0)
                    && to == owner()
                )
            ) && (jsonHash == auction.jsonHash);
    }
}

interface IWETH {
    function deposit() external payable;
    function transfer(address to, uint256 amount) external;
}
// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (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 Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        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);
    }
}

// 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;
    }
}