// errors
struct AuctionAlreadyEnded {
}

struct AuctionNotYetEnded {
}

struct AuctionEndAlreadyCalled {}

struct BidNotHighEnough {
    pub highest_bid: u256
}

// events
struct HighestBidIncreased {
    #indexed
    pub bidder: address
    pub amount: u256
}

struct AuctionEnded {
    #indexed
    pub winner: address
    pub amount: u256
}

contract Auction {
    // states
    auction_end_time: u256
    beneficiary: address

    highest_bidder: address
    highest_bid: u256

    pending_returns: Map<address, u256>

    ended: bool

    // constructor
    pub fn __init__(mut self, ctx: Context, bidding_time: u256, beneficiary_addr: address) {
        self.beneficiary = beneficiary_addr
        self.auction_end_time = ctx.block_timestamp() + bidding_time
    }

    //method
    pub fn bid(mut self, mut ctx: Context) {
        if ctx.block_timestamp() > self.auction_end_time {
            revert AuctionAlreadyEnded()
        }
        if ctx.msg_value() <= self.highest_bid {
            revert BidNotHighEnough(highest_bid: self.highest_bid)
        }
        if self.highest_bid != 0 {
            self.pending_returns[self.highest_bidder] += self.highest_bid
        }
        self.highest_bidder = ctx.msg_sender()
        self.highest_bid = ctx.msg_value()

        ctx.emit(HighestBidIncreased(bidder: ctx.msg_sender(), amount: ctx.msg_value()))
    }

    pub fn withdraw(mut self, mut ctx: Context) -> bool {
        let amount: u256 = self.pending_returns[ctx.msg_sender()]

        if amount > 0 {
            self.pending_returns[ctx.msg_sender()] = 0
            ctx.send_value(to: ctx.msg_sender(), wei: amount)
        }
        return true
    }

    pub fn auction_end(mut self, mut ctx: Context) {
        if ctx.block_timestamp() <= self.auction_end_time {
            revert AuctionNotYetEnded()
        }
        if self.ended {
            revert AuctionEndAlreadyCalled()
        }
        self.ended = true
        ctx.emit(AuctionEnded(winner: self.highest_bidder, amount: self.highest_bid))

        ctx.send_value(to: self.beneficiary, wei: self.highest_bid)
    }

    pub fn check_highest_bidder(self) -> address {
        return self.highest_bidder;
    }

    pub fn check_highest_bid(self) -> u256 {
        return self.highest_bid;
    }

    pub fn check_ended(self) -> bool {
        return self.ended;
    }
}