package openingbid

import org.scalatest._

class BidTest extends FunSuite {
  type Hand = scala.collection.immutable.IndexedSeq[Card]
  val debugging = true
  val runTestsOfUnassignedMethods = false
  
  def makeHand(cardString : String): Hand = {
    val cardArray = cardString.trim split (" ")
    val cards: List[Card] = for (c <- cardArray.toList) yield {
      val suit = (if (c.length == 2) c(1) else c(2)) match {
        case 'C' => "Clubs"
        case 'D' => "Diamonds"
        case 'H' => "Hearts"
        case 'S' => "Spades"
      }
      val value = c(0) match {
        case 'A' => 1
        case '1' => 10
        case 'J' => 11
        case 'Q' => 12
        case 'K' => 13
        case _ => c(0).toString.toInt
      }
      new Card(value, suit)
    }
    cards.toIndexedSeq
  }
  
  def makeHandWithDistribution(c: Int, d: Int, h: Int, s: Int) = {
    var cards = ""
    for (i <- 0 until c) cards += (i + 2) + "C "
    for (i <- 0 until d) cards += (i + 2) + "D "
    for (i <- 0 until h) cards += (i + 2) + "H "
    for (i <- 0 until s) cards += (i + 2) + "S "
    makeHand(cards.trim)
  }

  /** Creates a Hand according to the numbers specified.
   * @param suits How many of (Clubs, Diamonds, Hearts, Spades)
   * @param points High card points in (Clubs, Diamonds, Hearts, Spades)
   * @return A Hand with the correct number of high card points
   */
  def makeHand(suits: (Int, Int, Int, Int), points: (Int, Int, Int, Int)): Hand = {
    val (c, d, h, s) = suits
    assert((c+d+h+s) == 13)
    val (cp, dp, hp, sp) = points
    def makePoints(numCards: Int, points: Int, suit: Char) = {
      var rem = 0
      var cards = points match {
        case 10 => "A" + suit + " K" + suit + " Q" + suit + " J" + suit + " "
        case 9 => "A" + suit + " K" + suit + " Q" + suit + " "
        case 8 => "A" + suit + " K" + suit + " J" + suit + " "
        case 7 => "A" + suit + " K" + suit + " "
        case 6 => "A" + suit + " Q" + suit + " "
        case 5 => "K" + suit + " Q" + suit + " "
        case 4 => "A" + suit + " "
        case 3 => "K" + suit + " "
        case 2 => "Q" + suit + " "
        case 1 => "J" + suit + " "
        case 0 => ""
      }
      val remaining = numCards - (cards.length + 1) / 3
      for (i <- 2 until remaining + 2) cards += i.toString + suit + " "
      if (numCards == 0) "" else cards
    }
    makeHand(makePoints(c, cp, 'C') + makePoints(d, dp, 'D') + makePoints(h, hp, 'H') + makePoints(s, sp, 'S'))
  }
  
  test("Testing highCardPoints") {
    assertResult(12) { Bid.highCardPoints(makeHand("5C 7H AS JD 9D 2C KH 10H 4C 8H 8C AD 10C")) }
    assertResult(30) { Bid.highCardPoints(makeHand("JC QC QD KC KD KH AC AD AH AS 2C 3C 4C")) }
    assertResult(0) { Bid.highCardPoints(makeHand("2C 3C 4C 5C 2D 3D 4D 5D 2H 3H 4H 5H 2S")) }
    assertResult(0) { Bid.highCardPoints(makeHand("2C 3C 4C 5C 2D 3D 4D 5D 2H 3H 4H 5H 2S")) }
  }
  
  test("Testing distributionPoints") {
    assertResult(0) { Bid.distributionPoints(makeHand("2C 3C 4C 5C 2D 3D 4D 5D 2H 3H 4H 5H 2S")) }
    assertResult(1) { Bid.distributionPoints(makeHand("2C 3C 4C 5C 6C 3D 4D 5D 2H 3H 4H 5H 2S")) }
    assertResult(2) { Bid.distributionPoints(makeHand("2C 3C 4C 5C 6C 7C 4D 5D 2H 3H 4H 5H 2S")) }
    assertResult(2) { Bid.distributionPoints(makeHand("2C 3C 4C 5C 6C 3D 4D 5D 2H 3H 4H 5H 6H")) }
    assertResult(9) { Bid.distributionPoints(makeHand("2C 3C 4C 5C 6C 7C 8C 9C 10C JC QC KC AC")) }
  }
  
  test("Testing pointCount") {
    assertResult(0) { Bid.pointCount(makeHand("2C 3C 4C 5C 2D 3D 4D 5D 2H 3H 4H 5H 2S")) }
    assertResult(19) { Bid.pointCount(makeHand("2C 3C 4C 5C 6C 7C 8C 9C 10C JC QC KC AC")) }
  }

  // if (runTestsOfUnassignedMethods) {
    
  //   test("Testing suitCounts (unassigned method)") {
  //     assertResult(List(5, 3, 5, 0)) { Bid.suitCounts(makeHand("2C 3C 4C 5C 6C 3D 4D 5D 2H 3H 4H 5H 6H")) }
  //     assertResult(List(7, 3, 2, 1)) { Bid.suitCounts(makeHand("JC QC QD KC KD KH AC AD AH AS 2C 3C 4C")) }
  //   }
  
  // test("Testing highCardPoints in given suit (unassigned method)") {
  //   var hand = makeHand((4, 1, 3, 5), (8, 3, 4, 5))
  //   assertResult(8) { Bid.highCardPoints("Clubs", hand)}
  //   assertResult(3) { Bid.highCardPoints("Diamonds", hand)}
  //   assertResult(4) { Bid.highCardPoints("Hearts", hand)}
  //   assertResult(5) { Bid.highCardPoints("Spades", hand)}
  // }
    
  //   test("Testing between (unassigned method)") {
  //     assert(Bid.between(5, 5, 7))
  //     assert(Bid.between(6, 5, 7))
  //     assert(Bid.between(7, 5, 7))
  //     assert(! Bid.between(4, 5, 7))
  //     assert(! Bid.between(8, 5, 7))
  //   }
    
  //   test("Testing longer (unassigned method)") {
  //     val hand = makeHand("2C 3C 4C 2D 4D QD AH KH JH 2H 5H AS 9S")
  //     assert(Bid.longer("Hearts", "Spades", hand))
  //     assert(! Bid.longer("Clubs", "Diamonds", hand))
  //     assert(! Bid.longer("Spades", "Hearts", hand))
  //   }
  // }
  
  test("Testing balance") {
    assert(Bid.isBalanced(makeHandWithDistribution(3, 3, 3, 4)))
    assert(Bid.isBalanced(makeHandWithDistribution(3, 3, 3, 4)))
    assert(Bid.isBalanced(makeHandWithDistribution(3, 3, 3, 4)))
    assert(Bid.isBalanced(makeHandWithDistribution(3, 4, 2, 4)))
    assert(Bid.isBalanced(makeHandWithDistribution(2, 4, 3, 4)))
    assert(Bid.isBalanced(makeHandWithDistribution(5, 3, 2, 3)))
    assert(Bid.isBalanced(makeHandWithDistribution(3, 5, 2, 3)))
    assert(Bid.isBalanced(makeHandWithDistribution(2, 5, 3, 3)))

    assert(! Bid.isBalanced(makeHandWithDistribution(2, 3, 3, 5)))
    assert(! Bid.isBalanced(makeHandWithDistribution(2, 3, 5, 3)))
    assert(! Bid.isBalanced(makeHandWithDistribution(2, 1, 6, 4)))
    assert(! Bid.isBalanced(makeHandWithDistribution(4, 0, 0, 9)))
    assert(! Bid.isBalanced(makeHandWithDistribution(5, 4, 2, 2)))
  }
  
  def bidOn(cards: String) = Bid.bid(makeHand(cards))
  
  def bidOn(numCards: (Int, Int, Int, Int), points: (Int, Int, Int, Int)) =
    Bid.bid(makeHand(numCards, points))
  
  test("Testing 1NT bid") {
    assertResult("1NT") { bidOn("AC AD AH AS 2C 2D 2H 2S 3C 3D 3H 3S 4C") }
    assertResult("1NT") { bidOn("AC AD AH AS 2C 2D 2H 2S 3C 3D 3H 3S QC") }
    assertResult("1NT") { bidOn("AC AD AH AS 2C 2D 2H 2S 3C 3D 3H 4C 5C") }
    assertResult("1NT") { bidOn("AC AD AH AS 2C 2D 2H 2S 3C 3S 3H 4C 5C") }
    assertResult("1NT") { bidOn((3, 5, 3, 2), (4, 4, 4, 6)) }
  }
  
  test("Testing 1H bid") {
    assertResult("1H") { bidOn("2C 3C JC QC AD 5D 2H 3H 4H 5H JH KS QS") }
    assertResult("1H") { bidOn((3, 2, 5, 3), (4, 3, 4, 1)) }
    assertResult("1H") { bidOn((2, 1, 6, 4), (4, 4, 4, 7)) }
    assertResult("1H") { bidOn((2, 2, 5, 4), (4, 3, 4, 1)) }
    assertResult("1H") { bidOn((6, 0, 5, 2), (8, 0, 5, 5)) }
  }
  
  test("Testing 1S bid") {
    assertResult("1S") { bidOn((4, 2, 2, 5), (4, 3, 4, 1)) }
    assertResult("1S") { bidOn((3, 2, 3, 5), (4, 3, 4, 1)) }
    assertResult("1S") { bidOn((2, 1, 4, 6), (4, 4, 4, 7)) }
    assertResult("1S") { bidOn((2, 1, 5, 5), (4, 3, 4, 1)) }
    assertResult("1S") { bidOn((3, 0, 5, 5), (8, 0, 5, 5)) }
    assertResult("1S") { bidOn((3, 2, 2, 6), (8, 0, 0, 5)) }
  }
  
  test("Testing 1C with 19-20 bid") {
    assertResult("1C") { bidOn((3, 3, 4, 3), (4, 7, 4, 4)) }
    assertResult("1C") { bidOn((3, 2, 4, 4), (4, 4, 7, 4)) }
    assertResult("1C") { bidOn((3, 3, 4, 3), (4, 7, 4, 4)) }
    assertResult("1C") { bidOn((3, 2, 4, 4), (4, 7, 5, 4)) }
    assertResult("1C") { bidOn((4, 3, 4, 2), (6, 7, 3, 4)) }
    assertResult("1C") { bidOn((3, 2, 4, 4), (4, 7, 6, 3)) }
    assertResult("1C") { bidOn((5, 3, 3, 2), (4, 6, 6, 4)) }
  }
  
  test("Testing 1C with 13-21 bid") {
    assertResult("1C") { bidOn((3, 3, 4, 3), (4, 1, 4, 4)) }
    assertResult("1C") { bidOn((3, 2, 4, 4), (4, 1, 4, 4)) }
    assertResult("1C") { bidOn((3, 3, 4, 3), (4, 7, 4, 4)) }
    assertResult("1C") { bidOn((3, 2, 4, 4), (4, 7, 4, 4)) }
    assertResult("1C") { bidOn((3, 3, 4, 3), (6, 7, 4, 4)) }
    assertResult("1C") { bidOn((3, 2, 4, 4), (4, 7, 6, 4)) }
    assertResult("1C") { bidOn((5, 3, 3, 2), (4, 6, 6, 4)) }
  }
  
  test("Testing 1D with 13-21 bid") {
    assertResult("1D") { bidOn((4, 4, 3, 2), (4, 1, 4, 4)) }
    assertResult("1D") { bidOn((4, 4, 3, 2), (4, 7, 4, 4)) }
    assertResult("1D") { bidOn((4, 4, 3, 2), (4, 7, 4, 6)) }
    assertResult("1D") { bidOn((3, 5, 3, 2), (4, 6, 6, 4)) }
    assertResult("1D") { bidOn((3, 4, 3, 3), (4, 7, 6, 4)) }
  }
  
  test("Testing 2NT") {
    assertResult("2NT") { bidOn((5, 3, 3, 2), (4, 7, 6, 4)) }
    assertResult("2NT") { bidOn((5, 3, 3, 2), (4, 7, 6, 5)) }
    assertResult("2NT") { bidOn((3, 5, 3, 2), (4, 7, 6, 6)) }
    assertResult("2NT") { bidOn((2, 4, 4, 3), (4, 7, 6, 6)) }
  }
  
  test("Testing 2C with 22+ points and long Clubs") {
    assertResult("2C") { bidOn((5, 3, 4, 1), (4, 7, 6, 4)) }
    assertResult("2C") { bidOn((5, 3, 4, 1), (8, 7, 6, 4)) }
  }
  
  test("Testing 2D with 22+ points and long Diamonds") {
    assertResult("2D") { bidOn((3, 5, 4, 1), (4, 7, 6, 4)) }
    assertResult("2D") { bidOn((3, 5, 4, 1), (8, 7, 6, 4)) }
  }
  
  test("Testing 2H with 22+ points and long Hearts") {
    assertResult("2H") { bidOn((4, 1, 5, 3), (7, 4, 6, 4)) }
    assertResult("2H") { bidOn((4, 1, 5, 3), (8, 3, 6, 4)) }
  }
  
  test("Testing 2S with 22+ points and long Spades") {
    assertResult("2S") { bidOn((4, 1, 3, 5), (7, 4, 4, 6)) }
    assertResult("2S") { bidOn((4, 1, 3, 5), (8, 3, 5, 5)) }
  }
  
  test("Testing 2C with 22+ points and two long suits") {
    assertResult("2C") { bidOn((5, 5, 2, 1), (7, 4, 5, 4)) } // more high card points
    assertResult("2C") { bidOn((6, 5, 1, 1), (7, 7, 4, 4)) } // longer suit
    assertResult("2C") { bidOn((5, 5, 1, 2), (7, 7, 4, 4)) } // equal minor suits
    assertResult("2C") { bidOn((6, 6, 0, 1), (9, 9, 0, 0)) } // equal minor suits
  }
  
  test("Dividing by zero") {
    intercept[ArithmeticException] { 5 / 0 }
  }
  
  test("Testing 2D with 22+ points and two long suits") {
    assertResult("2D") { bidOn((5, 5, 2, 1), (5, 6, 5, 4)) } // more high card points
    assertResult("2D") { bidOn((5, 6, 1, 1), (7, 7, 4, 4)) } // longer suit
  }
  
  test("Testing 2H with 22+ points and two long suits") {
    assertResult("2H") { bidOn((5, 2, 5, 1), (5, 6, 5, 4)) }  // other long suit is minor
    assertResult("2H") { bidOn((1, 6, 5, 1), (4, 10, 4, 4)) } // other long suit is minor
    assertResult("2H") { bidOn((2, 1, 5, 5), (5, 4, 6, 5)) }  // more high card points
    assertResult("2H") { bidOn((1, 1, 6, 5), (4, 4, 6, 6)) }  // equal points, longer suit
    assertResult("2H") { bidOn((1, 2, 5, 5), (4, 4, 6, 6)) }  // otherwise, lower ranking suit
  }
  
  test("Testing 2S with 22+ points and two long suits") {
    assertResult("2S") { bidOn((5, 2, 1, 5), (5, 6, 5, 4)) }  // other long suit is minor
    assertResult("2S") { bidOn((1, 6, 1, 5), (4, 10, 4, 4)) } // other long suit is minor
    assertResult("2S") { bidOn((2, 1, 5, 5), (5, 4, 5, 6)) }  // more high card points
    assertResult("2S") { bidOn((1, 1, 5, 6), (4, 4, 6, 6)) }  // equal points, longer suit
  }
  
  test("Bid 3C preemptively with 7-card suit") {
    assertResult("3C") { bidOn((7, 2, 2, 2), (3, 1, 0, 1)) }
    assertResult("3C") { bidOn((7, 2, 1, 3), (3, 3, 0, 3)) }
  }
  
  test("Bid 3D preemptively with 7-card suit") {
    assertResult("3D") { bidOn((2, 7, 2, 2), (3, 1, 0, 1)) }
    assertResult("3D") { bidOn((2, 7, 1, 3), (3, 3, 0, 3)) }
  }
  
  test("Bid 3H preemptively with 7-card suit") {
    assertResult("3H") { bidOn((1, 1, 7, 4), (3, 1, 0, 1)) }
    assertResult("3H") { bidOn((2, 3, 7, 1), (2, 2, 2, 3)) }
  }
  
  test("Bid 3S preemptively with 7-card suit") {
    assertResult("3S") { bidOn((1, 1, 4, 7), (4, 1, 0, 0)) }
    assertResult("3S") { bidOn((3, 0, 3, 7), (3, 0, 2, 4)) }
  }
  
  test("Bid 4C preemptively with 8-card suit") {
    assertResult("4C") { bidOn((8, 1, 2, 2), (2, 2, 2, 0)) }
    assertResult("4C") { bidOn((8, 1, 4, 0), (4, 0, 4, 0)) }
  }
  
  test("Bid 4D preemptively with 8-card suit") {
    assertResult("4D") { bidOn((0, 8, 3, 2), (0, 3, 2, 1)) }
    assertResult("4D") { bidOn((2, 8, 1, 2), (0, 4, 0, 4)) }
  }
  
  test("Bid 4H preemptively with 8-card suit") {
    assertResult("4H") { bidOn((1, 2, 8, 2), (3, 2, 1, 0)) }
    assertResult("4H") { bidOn((3, 1, 8, 1), (3, 3, 1, 1)) }
  }
  
  test("Bid 4S preemptively with 8-card suit") {
    assertResult("4S") { bidOn((1, 2, 2, 8), (0, 1, 2, 3)) }
    assertResult("4S") { bidOn((1, 0, 4, 8), (1, 0, 3, 4)) }
  }
  
  def analyze(cards: String) {
    if (debugging) {
      val hand = makeHand(cards)
      val hcPoints = Bid.highCardPoints(hand)
      val distPoints = Bid.distributionPoints(hand)
      val isBal = if (Bid.isBalanced(hand)) "is" else "is not"
      println(s"$cards has $hcPoints high card points, $distPoints distribution points, and $isBal balanced.")
    }
  }
}
