package examples.actors

import java.util.Date
import scala.actors._

/** A simple demonstrator program implementing an online auction service
 *  The example uses the actor abstraction defined in the API of
 *  package scala.actors.
 */

trait AuctionMessage
case class Offer(bid: int, client: Actor) extends AuctionMessage // make a bid
case class Inquire(client: Actor) extends AuctionMessage         // inquire status

trait AuctionReply
case class Status(asked: int, expiration: Date)           // asked sum, expiration date
  extends AuctionReply
case object BestOffer extends AuctionReply                // yours is the best offer
case class BeatenOffer(maxBid: int) extends AuctionReply  // offer beaten by maxBid
case class AuctionConcluded(seller: Actor, client: Actor) // auction concluded
  extends AuctionReply
case object AuctionFailed extends AuctionReply            // failed with no bids
case object AuctionOver extends AuctionReply              // bidding is closed

class Auction(seller: Actor, minBid: int, closing: Date) extends Actor {
  val timeToShutdown = 3000 // msec
  val bidIncrement = 10

  def act() {
    var maxBid = minBid - bidIncrement
    var maxBidder: Actor = null

    loop {
      reactWithin (closing.getTime() - new Date().getTime()) {

        case Offer(bid, client) =>
          if (bid >= maxBid + bidIncrement) {
            if (maxBid >= minBid)
              maxBidder ! BeatenOffer(bid)
            maxBid = bid
            maxBidder = client
            client ! BestOffer
          }
          else
            client ! BeatenOffer(maxBid)

        case Inquire(client) =>
          client ! Status(maxBid, closing)

        case TIMEOUT =>
          if (maxBid >= minBid) {
            val reply = AuctionConcluded(seller, maxBidder)
            maxBidder ! reply
            seller ! reply
          } else {
            seller ! AuctionFailed
          }
          reactWithin(timeToShutdown) {
            case Offer(_, client) => client ! AuctionOver
            case TIMEOUT => exit()
          }

      }
    }
  }
}

object auction {

  val random = new java.util.Random()

  val minBid = 100
  val closing = new Date(new Date().getTime() + 4000)

  val seller = Actor.actor { }
  val auction = new Auction(seller, minBid, closing)

  def client(i: int, increment: int, top: int) = new Actor {
    val name = "Client " + i
    def log(msg: String) = Console.println(name + ": " + msg)
    var max: int = _
    var current: int = 0
    def act() {
      log("started")
      auction ! Inquire(this)
      receive {
        case Status(maxBid, _) =>
          log("status(" + maxBid + ")")
          max = maxBid
      }
      loop {
        if (max >= top) {
          log("too high for me")
        }
        else if (current < max) {
          current = max + increment
          Thread.sleep(1 + random.nextInt(1000))
          auction ! Offer(current, this)
        }

        reactWithin(3000) {
          case BestOffer =>
            log("bestOffer(" + current + ")")

          case BeatenOffer(maxBid) =>
            log("beatenOffer(" + maxBid + ")")
            max = maxBid

          case AuctionConcluded(seller, maxBidder) =>
            log("auctionConcluded"); exit()

          case AuctionOver =>
            log("auctionOver"); exit()

          case TIMEOUT =>
            exit()
        }
      }
    }
  }

  def main(args: Array[String]) = {
    seller.start()
    auction.start()
    client(1, 20, 200).start()
    client(2, 10, 300).start()
  }

}
