package bowling.acceptance;

import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.fail;

import org.junit.Test;

/**
 * Sub-class this and override the protected methods to make it work for your implementation
 */
public abstract class SingleGameScoring {
  
//  2.1.1 A game of tenpins consists of ten frames. A player delivers two balls in each of the first
//  nine frames unless a strike is scored. In the tenth frame, a player delivers three balls if a
//  strike or spare is scored. Every frame must be completed by each player bowling in regular
//  order.

  // We'll come back to the three frames thing because we don't know what a strike is yet  
  // and we only support one player for now

  @Test
  public void aGameOfTenPinsConsistsOfTenFrames() {
    assertThat(numberOfFrames(), is(10));
  }

  @Test
  public void aPlayerDeliversTwoBallsInEachFrame() {
    for(int frame = 0; frame < 10; frame++) {
      deliverBall(0);
      deliverBall(0);
    }
    assertThat(gameOver(), is(true));
  }
  
  @Test(expected=IllegalArgumentException.class) 
  public void shouldThrowAnExceptionIfTooManyPins() {
    deliverBall(11);
  }

  @Test(expected=IllegalArgumentException.class) 
  public void shouldThrowAnExceptionIfPinCountIsNegative() {
    deliverBall(-1);
  }



  
  
//  2.1.2 Except when a strike is scored, the number of pins knocked down by the player’s first delivery
//  is to be marked in the small square in the upper left-hand corner of that frame, and
//  the number of pins knocked down by the player’s second delivery is to be marked in the
//  upper right-hand corner. If none of the standing pins are knocked down by the second delivery
//  in the frame, the score sheet shall be marked with a (-). The count for the two deliveries
//  in the frame shall be recorded immediately.
  
  @Test
  public void theNumberofPinsKnockedDownByTheFirstDeliveryIsToBeMarkedInTheUpperLeftCorner() {
    deliverBall(3);
    assertThat(firstBallInFrame(1), is("3"));
  }

  @Test
  public void theNumberofPinsKnockedDownByTheSecondDeliveryIsToBeMarkedInTheUpperRightCorner() {
    deliverBall(3);
    deliverBall(4);
    assertThat(secondBallInFrame(1), is("4"));
  }
  
  @Test
  public void ifNoPinsAreKnockedDownByFirstBallMarkWithDash() {
    // The rules don't say what to do for the first gutter ball, but I am guessing it's a dash
    deliverBall(0);
    assertThat(firstBallInFrame(1), is("-"));
  }

  @Test
  public void ifNoPinsAreKnockedDownBySecondBallMarkWithDash() {
    deliverBall(3);
    deliverBall(0);
    assertThat(secondBallInFrame(1), is("-"));
  }

  @Test
  public void theCountForTheTwoDeliveriesShallBeShownImmediately() {
    deliverBall(3);
    deliverBall(6);
    assertThat(scoreForFrame(1), is("9"));
  }

  /// ...and conversely...
  
  @Test
  public void theCountShouldNotBeShownUntilTheFrameIsComplete() {
    deliverBall(3);
    assertThat(scoreForFrame(1), is(""));
  }

  
  // the rules don't say to accumulate the score for each frame, but the diagram does
  
  @Test
  public void everyFrameShouldShowTheCumulativeScore() {
    deliverBall(3);
    deliverBall(6);
    deliverBall(1);
    deliverBall(2);
    
    assertThat(scoreForFrame(1), is("9"));
    assertThat(scoreForFrame(2), is("12"));
  }

  // We should probably show the running total too
  
  @Test
  public void updateTheGameScoreAfterEveryDelivery() {
    deliverBall(3);
    assertThat(scoreForGame(), is("3"));

    deliverBall(6);
    assertThat(scoreForGame(), is("9"));

    deliverBall(1);
    assertThat(scoreForGame(), is("10"));
  }

  // still don't know what a strike is...

  


//  2.1.3 A strike is made when a full setup of pins is knocked down with the first delivery in a
//  frame. It is marked by an (X) in the small square in the upper left-hand corner of the frame
//  where it was made. The count for one strike is 10 plus the number of pins knocked down
//  on the player’s next two deliveries.
  
  // ...ah - strikes. We have some catching up to do
  
  @Test
  public void aStrikeIsTenPinsDownWithTheFirstBall() {
    deliverBall(10);
    assertThat(firstBallInFrame(1), is("X"));
  }
  
  
  @Test
  public void aStrikeScoresTenPlusTheNextTwoDeliveries() {
    deliverBall(10);
    deliverBall(6);
    deliverBall(3);
    assertThat(scoreForFrame(1), is("19"));
  }
  
  // the rules don't say where to put the other pins but we can clarify with a test   
  
  @Test
  public void thereIsOnlyOneBallInStrikeFrames() {
    deliverBall(10);
    deliverBall(6);
    deliverBall(3);
    
    assertThat(firstBallInFrame(1), is("X"));
    assertThat(secondBallInFrame(1), is(""));
    assertThat(firstBallInFrame(2), is("6"));
    assertThat(secondBallInFrame(2), is("3"));
  }
  

  // the rules don't say when to update the frame score after a strike but we can clarify with a test   
  @Test
  public void dontUpdateFrameScoreUntilAfterAdditionalDeliveries() {
    deliverBall(10);
    assertThat(scoreForFrame(1), is(""));

    deliverBall(6);
    deliverBall(3);
    assertThat(scoreForFrame(1), is("19"));
  }
  
  
//  2.1.4 Two consecutive strikes is a double. The count for the first strike is 20 plus the number of
//  pins knocked down with the first delivery following the second strike.

  // No new requirements here, but it provides an additional test
  
  @Test
  public void twoConsecutiveStrikesIsADouble() {
    deliverBall(10);
    deliverBall(10);
    deliverBall(3);

    assertThat(scoreForFrame(1), is("23"));
  }
  
//  2.1.5 Three successive strikes is a triple. The count for the first strike is 30. To bowl the maximum
//  score of 300, the player must bowl 12 strikes in succession.

  
  // Also not a new requirement
  // If I had David Saff's Popper, I'd write an @Theory for the maximum score
  
  @Test
  public void threeSuccessiveStrikesIsATriple() {
    deliverBall(10);
    deliverBall(10);
    deliverBall(10);
    
    assertThat(scoreForFrame(1), is("30"));
  }


  @Test
  public void twelveStrikesScoresThreeHundred() {
    for(int frame = 0; frame < 12; frame++) {
      deliverBall(10);
    }
    
    assertThat(scoreForFrame(10), is("300"));
  }


  
//  2.1.6 A spare is scored when pins left standing after the first delivery are knocked down with the
//  second delivery in that frame. It is marked by a (/) in the small square in the upper righthand
//  corner of the frame. The count for a spare is 10 plus the number of pins knocked
//  down by the player’s next delivery.}

  @Test
  public void aSpareIsScoredWhenRemainingPinsAreKnockedDownWithTheSecondBall() {
    deliverBall(7);
    deliverBall(3);
    assertThat(firstBallInFrame(1), is("7"));
    assertThat(secondBallInFrame(1), is("/"));
  }

  @Test
  public void aSpareScoresTenPlusTheNextDelivery() {
    deliverBall(7);
    deliverBall(3);
    deliverBall(4);
    assertThat(scoreForFrame(1), is("14"));
  }
  
  // It's time to revisit the last frame special cases 

  // 2.1.1 In the tenth frame, a player delivers three balls if a
  // strike or spare is scored.   
  @Test
  public void tenthFrameHasThreeBallsIfStrikeIsScored() {
    for(int frame = 0; frame < 10; frame++) {
      deliverBall(10);
    }
    
    deliverBall(4);
    deliverBall(3);

    assertThat(firstBallInFrame(10), is("X"));
    assertThat(secondBallInFrame(10), is("4"));
    assertThat(thirdBallInFrame(10), is("3"));
  }



  protected int numberOfFrames() {
    notImplementedYet("numberOfFrames");
    return -1;
  }

  protected boolean gameOver() {
    notImplementedYet("gameOver");
    return false;
  }

  protected void deliverBall(int i) {
    notImplementedYet("deliverBall");
  }
  
  protected String firstBallInFrame(int frameIndex) {
    notImplementedYet("firstBallInFrame");
    return null;
  }

  protected String secondBallInFrame(int frameIndex) {
    notImplementedYet("secondBallInFrame");
    return null;
  }

  protected String thirdBallInFrame(int frameIndex) {
    notImplementedYet("thirdBallInFrame");
    return null;
  }

  protected String scoreForFrame(int frameIndex) {
    notImplementedYet("scoreForFrame");
    return null;
  }

  protected String scoreForGame() {
    notImplementedYet("scoreForGame");
    return null;
  }

  protected void notImplementedYet(String feature) {
    fail(feature + " is not implemented yet");  
  }
}
