package examples.actors

import java.util.Date

import scala.actors._
import scala.actors.Actor._

/** 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 AuctionActor(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 scala.util.Random

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

  val seller = Actor.actor { }
  val auction = new AuctionActor(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()
  }

}
