Merge "croupier: Add Resume Game button"
diff --git a/lib/components/croupier.dart b/lib/components/croupier.dart
index bb7506a..770c7b7 100644
--- a/lib/components/croupier.dart
+++ b/lib/components/croupier.dart
@@ -50,6 +50,13 @@
                   child: new Text('Join Game', style: style.Text.titleStyle),
                   onPressed: makeSetStateCallback(
                       logic_croupier.CroupierState.JoinGame)),
+              new FlatButton(
+                  child: new Text('Resume Game', style: style.Text.titleStyle),
+                  onPressed: config.croupier.mostRecentGameID != null
+                      ? makeSetStateCallback(
+                          logic_croupier.CroupierState.ResumeGame,
+                          config.croupier.mostRecentGameID)
+                      : null),
               new CroupierProfileComponent(
                   settings: config.croupier.settings,
                   width: style.Size.settingsSize,
@@ -118,13 +125,18 @@
       case logic_croupier.CroupierState.PlayGame:
         return new Container(
             padding: new EdgeDims.only(top: ui.window.padding.top),
-            child: component_game.createGameComponent(config.croupier, () {
-              config.croupier.game.quit();
-              makeSetStateCallback(logic_croupier.CroupierState.Welcome)();
-            },
+            child: component_game.createGameComponent(config.croupier,
+                makeSetStateCallback(logic_croupier.CroupierState.Welcome),
                 width: ui.window.size.width,
                 height: ui.window.size.height - ui.window.padding.top,
                 key: _gameKey));
+
+      case logic_croupier.CroupierState.ResumeGame:
+        return new Container(
+            padding: new EdgeDims.only(top: ui.window.padding.top),
+            child: new Text("Resuming Game...", style: style.Text.titleStyle),
+            width: ui.window.size.width,
+            height: ui.window.size.height - ui.window.padding.top);
       default:
         assert(false);
         return null;
diff --git a/lib/components/hearts/hearts.part.dart b/lib/components/hearts/hearts.part.dart
index 01f223b..d74946b 100644
--- a/lib/components/hearts/hearts.part.dart
+++ b/lib/components/hearts/hearts.part.dart
@@ -301,7 +301,9 @@
 
   Widget _makeDebugButtons() {
     if (config.game.debugMode == false) {
-      return new Flex([]);
+      return new Flex([
+        new Flexible(flex: 4, child: _makeButton('Quit', _quitGameCallback))
+      ]);
     }
     return new Container(
         width: config.width,
diff --git a/lib/components/proto/proto.part.dart b/lib/components/proto/proto.part.dart
index 79e1e7f..2e1bd88 100644
--- a/lib/components/proto/proto.part.dart
+++ b/lib/components/proto/proto.part.dart
@@ -17,7 +17,7 @@
   Widget build(BuildContext context) {
     List<Widget> cardCollections = new List<Widget>();
 
-    cardCollections.add(new Text(config.game.debugString));
+    cardCollections.add(new Text(config.game.debugString ?? ""));
 
     for (int i = 0; i < 4; i++) {
       List<logic_card.Card> cards = config.game.cardCollections[i];
@@ -60,7 +60,9 @@
 
   Widget _makeDebugButtons() {
     if (config.game.debugMode == false) {
-      return new Flex([]);
+      return new Flex([
+        new Flexible(flex: 4, child: _makeButton('Quit', _quitGameCallback))
+      ]);
     }
     return new Flex([
       new Text('P${config.game.playerNumber}'),
diff --git a/lib/components/solitaire/solitaire.part.dart b/lib/components/solitaire/solitaire.part.dart
index a9833db..28c071b 100644
--- a/lib/components/solitaire/solitaire.part.dart
+++ b/lib/components/solitaire/solitaire.part.dart
@@ -58,7 +58,9 @@
 
   Widget _makeDebugButtons() {
     if (config.game.debugMode == false) {
-      return new Flex([]);
+      return new Flex([
+        new Flexible(flex: 4, child: _makeButton('Quit', _quitGameCallback))
+      ]);
     }
     return new Container(
         width: config.width,
diff --git a/lib/logic/create_game.dart b/lib/logic/create_game.dart
index 1a2f95c..fbad529 100644
--- a/lib/logic/create_game.dart
+++ b/lib/logic/create_game.dart
@@ -8,17 +8,21 @@
 import 'solitaire/solitaire.dart' as solitaire_impl;
 
 game_impl.Game createGame(game_impl.GameType gt, bool debugMode,
-    {int gameID, bool isCreator}) {
+    {int gameID, bool isCreator, int playerNumber}) {
   switch (gt) {
     case game_impl.GameType.Proto:
       return new proto_impl.ProtoGame(gameID: gameID, isCreator: isCreator)
-        ..debugMode = debugMode;
+        ..debugMode = debugMode
+        ..playerNumber = playerNumber;
     case game_impl.GameType.Hearts:
       return new hearts_impl.HeartsGame(gameID: gameID, isCreator: isCreator)
-        ..debugMode = debugMode;
+        ..debugMode = debugMode
+        ..playerNumber = playerNumber;
     case game_impl.GameType.Solitaire:
       return new solitaire_impl.SolitaireGame(
-          gameID: gameID, isCreator: isCreator)..debugMode = debugMode;
+          gameID: gameID, isCreator: isCreator)
+        ..debugMode = debugMode
+        ..playerNumber = playerNumber;
     default:
       assert(false);
       return null;
diff --git a/lib/logic/croupier.dart b/lib/logic/croupier.dart
index e3de247..53d1824 100644
--- a/lib/logic/croupier.dart
+++ b/lib/logic/croupier.dart
@@ -12,7 +12,14 @@
 import 'game/game.dart'
     show Game, GameType, GameStartData, stringToGameType, gameTypeToString;
 
-enum CroupierState { Welcome, ChooseGame, JoinGame, ArrangePlayers, PlayGame }
+enum CroupierState {
+  Welcome,
+  ChooseGame,
+  JoinGame,
+  ArrangePlayers,
+  PlayGame,
+  ResumeGame
+}
 
 typedef void NoArgCb();
 
@@ -26,6 +33,7 @@
   Map<String, GameStartData> games_found; // empty, but loads asynchronously
   Map<int, int> players_found; // empty, but loads asynchronously
   Game game; // null until chosen
+  int mostRecentGameID; // null until a game was started.
   NoArgCb informUICb;
 
   // Futures to use in order to cancel scans and advertisements.
@@ -83,6 +91,27 @@
         orElse: () => null);
   }
 
+  void _setCurrentGame(Game g) {
+    game = g;
+    mostRecentGameID = game.gameID;
+  }
+
+  Game _createNewGame(GameType gt) {
+    return cg.createGame(gt, this.debugMode, isCreator: true);
+  }
+
+  Game _createExistingGame(GameStartData gsd) {
+    return cg.createGame(stringToGameType(gsd.type), this.debugMode,
+        gameID: gsd.gameID, playerNumber: gsd.playerNumber);
+  }
+
+  void _quitGame() {
+    if (game != null) {
+      game.quit();
+      game = null;
+    }
+  }
+
   CroupierSettings settingsFromPlayerNumber(int playerNumber) {
     int userID = userIDFromPlayerNumber(playerNumber);
     if (userID != null) {
@@ -142,8 +171,10 @@
   void setState(CroupierState nextState, var data) {
     switch (state) {
       case CroupierState.Welcome:
-        // data should be empty.
-        assert(data == null);
+        // data should be empty unless nextState is ResumeGame.
+        if (nextState != CroupierState.ResumeGame) {
+          assert(data == null);
+        }
         break;
       case CroupierState.ChooseGame:
         if (data == null) {
@@ -154,11 +185,14 @@
 
         // data should be the game id here.
         GameType gt = data as GameType;
-        game = cg.createGame(gt, this.debugMode, isCreator: true);
+        _setCurrentGame(_createNewGame(gt));
 
         _advertiseFuture = settings_manager
             .createGameSyncgroup(gameTypeToString(gt), game.gameID)
             .then((GameStartData gsd) {
+          if (!game.gameArrangeData.needsArrangement) {
+            settings_manager.setPlayerNumber(gsd.gameID, 0);
+          }
           // Only the game chooser should be advertising the game.
           return settings_manager.advertiseSettings(gsd);
         }); // don't wait for this future.
@@ -179,8 +213,8 @@
 
         // data would probably be the game id again.
         GameStartData gsd = data as GameStartData;
-        game = cg.createGame(stringToGameType(gsd.type), this.debugMode,
-            gameID: gsd.gameID);
+        gsd.playerNumber = null; // At first, there is no player number.
+        _setCurrentGame(_createExistingGame(gsd));
         String sgName;
         games_found.forEach((String name, GameStartData g) {
           if (g == gsd) {
@@ -189,11 +223,13 @@
         });
         assert(sgName != null);
 
-        settings_manager.joinGameSyncgroup(sgName, gsd.gameID);
         players_found[gsd.ownerID] = null;
-        if (!game.gameArrangeData.needsArrangement) {
-          settings_manager.setPlayerNumber(gsd.gameID, 0);
-        }
+        settings_manager.joinGameSyncgroup(sgName, gsd.gameID).then((_) {
+          if (!game.gameArrangeData.needsArrangement) {
+            settings_manager.setPlayerNumber(gsd.gameID, 0);
+          }
+        });
+
         break;
       case CroupierState.ArrangePlayers:
         // Note that if we were arranging players, we might have been advertising.
@@ -209,7 +245,13 @@
         assert(data == null);
         break;
       case CroupierState.PlayGame:
-        // data should be empty.
+        break;
+      case CroupierState.ResumeGame:
+        // Data might be GameStartData. If so, then we must advertise it.
+        GameStartData gsd = data;
+        if (gsd != null) {
+          _advertiseFuture = settings_manager.advertiseSettings(gsd);
+        }
         break;
       default:
         assert(false);
@@ -217,16 +259,62 @@
 
     // A simplified way of clearing out the games and players found.
     // They will need to be re-discovered in the future.
-    if (nextState == CroupierState.Welcome) {
-      games_found.clear();
-      players_found.clear();
-      game = null;
-    } else if (nextState == CroupierState.JoinGame) {
-      // Start scanning for games since that's what's next for you.
-      _scanFuture =
-          settings_manager.scanSettings(); // don't wait for this future.
+    switch (nextState) {
+      case CroupierState.Welcome:
+        games_found.clear();
+        players_found.clear();
+        _quitGame();
+        break;
+      case CroupierState.JoinGame:
+        // Start scanning for games since that's what's next for you.
+        _scanFuture =
+            settings_manager.scanSettings(); // don't wait for this future.
+        break;
+      case CroupierState.ResumeGame:
+        // We need to create the game again.
+        int gameIDData = data;
+        _resumeGameAsynchronously(gameIDData);
+        break;
+      default:
+        break;
     }
 
     state = nextState;
   }
+
+  // Resumes the game from the given gameID.
+  Future _resumeGameAsynchronously(int gameIDData) async {
+    GameStartData gsd = await settings_manager.getGameStartData(gameIDData);
+    bool wasOwner = (gsd.ownerID == settings?.userID);
+    print(
+        "The game was ${gsd.toJSONString()}, and was I the owner? ${wasOwner}");
+    _setCurrentGame(_createExistingGame(gsd));
+
+    String sgName = await settings_manager.getGameSyncgroup(gameIDData);
+    print("The sg name was ${sgName}");
+    await settings_manager.joinGameSyncgroup(sgName, gameIDData);
+
+    // Since initial scan processing is done, we can now set isCreator
+    game.isCreator = wasOwner;
+    String gameStatus = await settings_manager.getGameStatus(gameIDData);
+
+    print("The game's status was ${gameStatus}");
+    // Depending on the game state, we should go to a different screen.
+    switch (gameStatus) {
+      case "RUNNING":
+        // The game is running, so let's play it!
+        setState(CroupierState.PlayGame, null);
+        break;
+      default:
+        // We are still arranging players, so we need to advertise our game
+        // start data.
+        setState(CroupierState.ArrangePlayers, gsd);
+        break;
+    }
+
+    // And we can ask the UI to redraw
+    if (this.informUICb != null) {
+      this.informUICb();
+    }
+  }
 }
diff --git a/lib/logic/game/game_def.part.dart b/lib/logic/game/game_def.part.dart
index c7544e5..43deaa6 100644
--- a/lib/logic/game/game_def.part.dart
+++ b/lib/logic/game/game_def.part.dart
@@ -91,7 +91,7 @@
   GameArrangeData get gameArrangeData;
   final GameType gameType;
   String get gameTypeName; // abstract
-  final bool isCreator;
+  bool isCreator; // True if this user created the game. Behavior can vary based on this flag, so it can make sense to defer setting it.
 
   final List<List<Card>> cardCollections = new List<List<Card>>();
   final List<Card> deck = new List<Card>.from(Card.All);
diff --git a/lib/src/mocks/settings_manager.dart b/lib/src/mocks/settings_manager.dart
index 533fead..e0b1daa 100644
--- a/lib/src/mocks/settings_manager.dart
+++ b/lib/src/mocks/settings_manager.dart
@@ -77,4 +77,16 @@
   Future setGameStatus(int gameID, String status) async {
     return new Future(() => null);
   }
+
+  Future<String> getGameStatus(int gameID) async {
+    return new Future(() => null);
+  }
+
+  Future<logic_game.GameStartData> getGameStartData(int gameID) async {
+    return new Future(() => null);
+  }
+
+  Future<String> getGameSyncgroup(int gameID) async {
+    return new Future(() => null);
+  }
 }
diff --git a/lib/src/syncbase/settings_manager.dart b/lib/src/syncbase/settings_manager.dart
index 55a71b8..ef40e49 100644
--- a/lib/src/syncbase/settings_manager.dart
+++ b/lib/src/syncbase/settings_manager.dart
@@ -169,9 +169,11 @@
     logic_game.GameStartData gsd =
         new logic_game.GameStartData(type, 0, gameID, id);
 
-    await _cc.createSyncgroup(
-        _cc.makeSyncgroupName(util.syncgameSuffix("${gsd.gameID}")),
-        util.tableNameGames,
+    String sgName = _cc.makeSyncgroupName(util.syncgameSuffix("${gsd.gameID}"));
+
+    await gameTable.row(util.gameSyncgroupKey(gameID)).put(UTF8.encode(sgName));
+
+    await _cc.createSyncgroup(sgName, util.tableNameGames,
         prefix: util.syncgamePrefix(gameID));
 
     return gsd;
@@ -212,6 +214,34 @@
     await gameTable.row(util.gameStatusKey(gameID)).put(UTF8.encode(status));
   }
 
+  Future<String> getGameStatus(int gameID) async {
+    sc.SyncbaseDatabase db = await _cc.createDatabase();
+    sc.SyncbaseTable gameTable = await _cc.createTable(db, util.tableNameGames);
+
+    return _tryReadData(gameTable, util.gameStatusKey(gameID));
+  }
+
+  Future<String> getGameSyncgroup(int gameID) async {
+    sc.SyncbaseDatabase db = await _cc.createDatabase();
+    sc.SyncbaseTable gameTable = await _cc.createTable(db, util.tableNameGames);
+    return _tryReadData(gameTable, util.gameSyncgroupKey(gameID));
+  }
+
+  Future<logic_game.GameStartData> getGameStartData(int gameID) async {
+    sc.SyncbaseDatabase db = await _cc.createDatabase();
+    sc.SyncbaseTable gameTable = await _cc.createTable(db, util.tableNameGames);
+
+    String owner = await _tryReadData(gameTable, util.gameOwnerKey(gameID));
+    String type = await _tryReadData(gameTable, util.gameTypeKey(gameID));
+
+    int id = await _getUserID();
+    String playerNumber =
+        await _tryReadData(gameTable, util.playerNumberKeyFromData(gameID, id));
+    int pn = playerNumber != null ? int.parse(playerNumber) : null;
+
+    return new logic_game.GameStartData(type, pn, gameID, int.parse(owner));
+  }
+
   // TODO(alexfandrianto): It is possible that the more efficient way of
   // scanning is to do it for only short bursts. In that case, we should call
   // stopScanSettings a few seconds after starting it.
diff --git a/lib/src/syncbase/util.dart b/lib/src/syncbase/util.dart
index af882ba..c6c8259 100644
--- a/lib/src/syncbase/util.dart
+++ b/lib/src/syncbase/util.dart
@@ -77,6 +77,10 @@
   return "${gameID}/status";
 }
 
+String gameSyncgroupKey(int gameID) {
+  return "${gameID}/game_sg";
+}
+
 String playerSettingsKeyFromData(int gameID, int userID) {
   return "${gameID}/players/${userID}/settings_sg";
 }
diff --git a/schema.md b/schema.md
index 8c01264..718055e 100644
--- a/schema.md
+++ b/schema.md
@@ -11,6 +11,7 @@
 
 ```
 For advertising and setting up the game:
+<game_id>/game_sg = <game_syncgroup_name>
 <game_id>/type = <game_type>
 <game_id>/owner = <user_id>
 <game_id>/status = [null|RUNNING]