content: package rabbitescape.ui.android;
  
  import android.app.AlertDialog;
  import android.content.Intent;
  import android.content.res.Resources;
  import android.os.Bundle;
  import android.view.View;
  import android.widget.Button;
  import android.widget.LinearLayout;
  import android.widget.RadioGroup;
  import android.widget.TextView;
  
  import java.util.HashMap;
  import java.util.Map;
  import java.util.Set;
  
  import rabbitescape.engine.CompletedLevelWinListener;
  import rabbitescape.engine.LevelWinListener;
  import rabbitescape.engine.LoadWorldFile;
  import rabbitescape.engine.MultiLevelWinListener;
  import rabbitescape.engine.Token;
  import rabbitescape.engine.World;
  import rabbitescape.engine.WorldStatsListener;
  import rabbitescape.engine.err.RabbitEscapeException;
  import rabbitescape.engine.menu.ByNameConfigBasedLevelsCompleted;
  import rabbitescape.engine.menu.LevelsCompleted;
  import rabbitescape.engine.menu.LevelsList;
  import rabbitescape.engine.menu.LoadLevelsList;
  import rabbitescape.engine.menu.MenuDefinition;
  import rabbitescape.engine.textworld.TextWorldManip;
  import rabbitescape.engine.util.RealFileSystem;
  
  import static android.text.TextUtils.split;
  import static rabbitescape.engine.i18n.Translation.t;
  
  import net.artificialworlds.rabbitescape.R;
  
  public class AndroidGameActivity extends RabbitEscapeActivity
      implements NumLeftListener, WorldStatsListener
  {
      // Constants
      public static final String INTENT_LEVELS_DIR   = "rabbitescape.levelsdir";
      public static final String INTENT_LEVEL        = "rabbitescape.level";
      public static final String INTENT_LEVEL_NUMBER = "rabbitescape.levelnumber";
      public static final String INTENT_LEVELSET     = "rabbitescape.levelset";
      public static final String INTENT_LASTINSET    = "rabbitescape.lastlevel";
      public static final String STATE_CHECKED_ABILITY_INDEX = "rabbitescape.checkedAbilityIndex";
  
      public AlertDialog currentDialog;
      public RadioGroup abilitiesGroup;
  
      // System
      private LevelsCompleted levelsCompleted;
  
      private Button muteButton;
      private Button pauseButton;
      private Button speedButton;
      private LinearLayout topLayout;
  
      public GameSurfaceView gameSurface;
      private Token.Type[] abilities;
      private TextView worldStatsTextView;
      public World world;
      public Game game;
  
      @Override
      protected void onCreate( Bundle savedInstanceState )
      {
          super.onCreate( savedInstanceState );
  
          Intent intent = getIntent();
          String levelsDir     = intent.getStringExtra( INTENT_LEVELS_DIR );
          String levelFileName = intent.getStringExtra( INTENT_LEVEL );
          int    levelNum      = intent.getIntExtra( INTENT_LEVEL_NUMBER, 0 );
          String levelSet      = intent.getStringExtra( INTENT_LEVELSET );
          boolean lastInSet    = intent.getBooleanExtra( INTENT_LASTINSET, false );
  
          Resources resources = getResources();
  
          staticInit();
          world = loadWorld( levelFileName, savedInstanceState );
  
          sound.setMusic( world.music );
  
          LevelWinListener winListener = new MultiLevelWinListener(
              new CompletedLevelWinListener( levelsDir, levelNum, levelsCompleted ),
              new AndroidAlertWinListener( this, levelSet, lastInSet )
          );
  
          game = new Game(
              SingletonBitmapCache.instance( resources ),
              getConfig(),
              world,
              winListener,
              savedInstanceState
          );
  
          buildDynamicUi( resources, game, world, savedInstanceState );
  
          if ( savedInstanceState == null )
          {
              Dialogs.intro( this, world );
          }
          else
          {
              restoreFromState( savedInstanceState );
          }
      }
  
      @Override
      protected void onResume()
      {
          super.onResume();
          sound.setMusic( world.music );
      }
  
      private World loadWorld( String levelFileName, Bundle savedInstanceState )
      {
          if ( savedInstanceState != null )
          {
              String savedWorld = savedInstanceState.getString( AndroidGameLaunch.STATE_WORLD );
              if ( savedWorld != null )
              {
                  return TextWorldManip.createWorld( this, split( savedWorld, "\n" ) );
              }
          }
  
          return new LoadWorldFile( new RealFileSystem() ).load( this, levelFileName );
      }
  
      private void buildDynamicUi(
          Resources resources,
          Game game,
          World world,
          Bundle savedInstanceState
      )
      {
          createAbilities( world, resources );
          updatePauseButton( world.paused );
  
          gameSurface = new GameSurfaceView( this, this, sound, game, world );
  
          if ( savedInstanceState != null )
          {
              // Get the "fast" state from savedInstanceState because we have no
              // game yet - that won't be created until we have a surface.
  
              updateSpeedButton(
                  savedInstanceState.getBoolean( AndroidGameLaunch.STATE_FAST_PRESSED, false ) );
          }
  
          topLayout.addView( gameSurface );
      }
  
      private void staticInit()
      {
          LevelsList levelsList = LoadLevelsList.load( MenuDefinition.allLevels );
          levelsCompleted = new ByNameConfigBasedLevelsCompleted( getConfig(), levelsList );
  
          setContentView( R.layout.activity_android_game );
  
          muteButton = (Button)findViewById( R.id.muteButton );
          pauseButton = (Button)findViewById( R.id.pauseButton );
          speedButton = (Button)findViewById( R.id.speedButton );
          topLayout = (LinearLayout)findViewById( R.id.topLayout );
          abilitiesGroup = (RadioGroup)findViewById( R.id.abilitiesGroup );
          worldStatsTextView = (TextView)findViewById( R.id.worldStats );
      }
  
      private void createAbilities( World world, Resources resources )
      {
          Set<Map.Entry<Token.Type, Integer>> entries = world.abilities.entrySet();
          abilities = new Token.Type[ entries.size() ];
  
          int i = 0;
          for ( Map.Entry<Token.Type, Integer> entry : entries )
          {
              abilities[i] = entry.getKey();
              abilitiesGroup.addView(
                  new AbilityButton(
                      this,
                      resources,
                      abilitiesGroup,
                      entry.getKey().name(),
                      entry.getValue(),
                      i
                  )
              );
              ++i;
          }
  
          abilitiesGroup.setOnCheckedChangeListener(
              new RadioGroup.OnCheckedChangeListener()
              {
                  @Override
                  public void onCheckedChanged( RadioGroup radioGroup, int buttonIndex )
                  {
                      game.gameLaunch.chosenAbility = abilities[buttonIndex];
  
                      for ( int i = 0; i < radioGroup.getChildCount(); i++ )
                      {
                          AbilityButton button = (AbilityButton)( radioGroup.getChildAt( i ) );
  
                          button.setChecked( i == buttonIndex );
                      }
                  }
              }
          );
      }
  
      @Override
      public void updateMuteButton( boolean muted )
      {
          muteButton.setCompoundDrawablesWithIntrinsicBounds(
              getResources().getDrawable( muted ? R.drawable.menu_muted : R.drawable.menu_unmuted ),
              null,
              null,
              null
          );
          muteButton.invalidate();
      }
  
      public void onPauseClicked( View view )
      {
          updatePauseButton( gameSurface.togglePaused() );
      }
  
      private void updatePauseButton( boolean paused )
      {
          pauseButton.setCompoundDrawablesWithIntrinsicBounds(
              getResources().getDrawable( paused ? R.drawable.menu_unpause : R.drawable.menu_pause ),
              null,
              null,
              null
          );
          pauseButton.invalidate();
  
          // Show Overlay
          game.gameLaunch.graphics.redraw();
      }
  
      public void onSpeedClicked( View view )
      {
          updateSpeedButton( gameSurface.toggleSpeed() );
      }
  
      private void updateSpeedButton( boolean fast )
      {
          int speedRes = fast ? R.drawable.menu_speedup_active : R.drawable.menu_speedup_inactive;
  
          speedButton.setCompoundDrawablesWithIntrinsicBounds(
              getResources().getDrawable( speedRes ), null, null, null );
  
          speedButton.invalidate();
      }
  
      public void onExplodeAllClicked( View view )
      {
          World world = game.gameLaunch.physics.world;
  
          switch ( world.completionState() )
          {
              case RUNNING:
              case PAUSED:
              {
                  Dialogs.explode( this, world );
                  break;
              }
              default:
              {
                  // Don't do anything if we've finished already
                  break;
              }
          }
      }
  
      public void setPaused( World world, boolean paused )
      {
          world.setPaused( paused );
          updatePauseButton( paused );
      }
  
      @Override
      public void onSaveInstanceState( Bundle outState )
      {
          super.onSaveInstanceState( outState );
          // The super call will call onSaveInstanceState on:
          // - GameSurfaceView
          // - each AbilityButton (but we do the saving here because it's easier
  
          outState.putInt( STATE_CHECKED_ABILITY_INDEX, checkedAbilityIndex() );
  
          game.gameLaunch.onSaveInstanceState( outState );
  
          // Mute state is stored in a preference, so no need to store it here.
      }
  
      private void restoreFromState( Bundle savedInstanceState )
      {
          int checkedAbility = savedInstanceState.getInt( STATE_CHECKED_ABILITY_INDEX, -1 );
          if ( checkedAbility != -1 )
          {
              abilitiesGroup.check( checkedAbility );
          }
      }
  
      private int checkedAbilityIndex()
      {
          for ( int i = 0; i < abilitiesGroup.getChildCount(); i++ )
          {
              AbilityButton button = (AbilityButton)( abilitiesGroup.getChildAt( i ) );
  
              if ( button.isChecked() )
              {
                  return i;
              }
          }
  
          return -1;
      }
  
      @Override
      public void numLeft( Token.Type ability, int numLeft )
      {
          AbilityButton button = buttonForAbility( ability );
  
          button.setNumLeft( numLeft );
  
          if ( numLeft == 0 )
          {
              button.disable();
              button.setChecked( false );
          }
      }
  
      @Override
      public void worldStats( int num_saved, int num_to_save )
      {
          final Map<String, Object> values = new HashMap<String, Object>();
          values.put( "num_saved", num_saved );
          values.put( "num_to_save", num_to_save );
  
          this.runOnUiThread(
              new Runnable()
              {
                  @Override
                  public void run()
                  {
                      worldStatsTextView.setText( t( "${num_saved} / ${num_to_save}", values ) );
                  }
              }
          );
      }
  
      public static class UnrecognisedAbility extends RabbitEscapeException
      {
          public final Token.Type ability;
  
          public UnrecognisedAbility( Token.Type ability )
          {
              this.ability = ability;
          }
      }
  
      private AbilityButton buttonForAbility( Token.Type ability )
      {
          int i = 0;
          for ( Token.Type ab : abilities )
          {
              if ( ab == ability )
              {
                  return (AbilityButton)abilitiesGroup.getChildAt( i );
              }
              ++i;
          }
          throw new UnrecognisedAbility( ability );
      }
  }
  