content: package rabbitescape.ui.swing;
  
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Container;
  import java.awt.Dimension;
  import java.awt.GridBagConstraints;
  import java.awt.GridBagLayout;
  import java.awt.Insets;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.awt.event.KeyEvent;
  import java.io.File;
  import java.io.FileNotFoundException;
  import java.io.PrintStream;
  import java.io.PrintWriter;
  import java.util.Locale;
  import java.util.Stack;
  
  import javax.swing.JButton;
  import javax.swing.JFileChooser;
  import javax.swing.JLabel;
  import javax.swing.JOptionPane;
  import javax.swing.JPanel;
  import javax.swing.JScrollPane;
  import javax.swing.JTextPane;
  import javax.swing.SwingConstants;
  import javax.swing.SwingUtilities;
  import javax.swing.SwingWorker;
  import javax.swing.filechooser.FileNameExtensionFilter;
  
  import static rabbitescape.engine.util.Util.*;
  import static rabbitescape.engine.i18n.Translation.*;
  import static rabbitescape.ui.swing.SwingConfigSetup.*;
  
  import rabbitescape.engine.CompletedLevelWinListener;
  import rabbitescape.engine.IgnoreLevelWinListener;
  import rabbitescape.engine.LevelWinListener;
  import rabbitescape.engine.MultiLevelWinListener;
  import rabbitescape.engine.config.Config;
  import rabbitescape.engine.config.ConfigKeys;
  import rabbitescape.engine.config.ConfigTools;
  import rabbitescape.engine.config.TapTimer;
  import rabbitescape.engine.err.RabbitEscapeException;
  import rabbitescape.engine.menu.*;
  import rabbitescape.engine.util.RealFileSystem;
  import rabbitescape.engine.util.Util.IdxObj;
  import rabbitescape.render.BitmapCache;
  import rabbitescape.render.androidlike.Sound;
  
  public class MenuUi
  {
      public static class UnknownMenuItemType extends RabbitEscapeException
      {
          private static final long serialVersionUID = 1L;
  
          public final String name;
          public final MenuItem.Type type;
  
          public UnknownMenuItemType( MenuItem item )
          {
              this.name = item.name;
              this.type = item.type;
          }
      }
  
      private class ButtonListener implements ActionListener
      {
          private final MenuItem item;
          private final Menu menu;
  
          public ButtonListener( MenuItem item, Menu menu )
          {
              this.item = item;
              this.menu = menu;
          }
  
          @Override
          public void actionPerformed( ActionEvent event )
          {
              switch ( item.type )
              {
                  case MENU:
                  {
                      SwingUtilities.invokeLater( new Runnable()
                      {
                          @Override
                          public void run()
                          {
                              stack.add( item.menu );
                              placeMenu();
                          }
                      } );
                      return;
                  }
                  case ABOUT:
                  {
                      about();
                      return;
                  }
                  case LEVEL:
                  {
                      level( (LevelMenuItem)item, (LevelsMenu)menu );
                      return;
                  }
                  case LOAD:
                  {
                      chooseLevel();
                      return;
                  }
                  case GITHUB_ISSUE:
                  {
                      chooseIssue();
                      return;
                  }
                  case QUIT:
                  {
                      frame.exit();
                      return;
                  }
                  case DEMO:
                  {
                      return;
                  }
                  default:
                  {
                      throw new UnknownMenuItemType( item );
                  }
              }
          }
      }
  
      private static final Color backgroundColor = Color.WHITE;
      private static final Color buttonColor = Color.LIGHT_GRAY;
  
      private final RealFileSystem fs;
      private final PrintStream out;
      private final Locale locale;
      private final BitmapCache<SwingBitmap> bitmapCache;
  
      private final Stack<Menu> stack;
      private final Config uiConfig;
      private final MainJFrame frame;
      private final Sound sound;
  
      private final JPanel menuPanel;
      private final LevelsCompleted levelsCompleted;
      private SideMenu sidemenu;
  
      public MenuUi(
          RealFileSystem fs,
          PrintStream out,
          Locale locale,
          BitmapCache<SwingBitmap> bitmapCache,
          Config uiConfig,
          MainJFrame frame,
          Sound sound
      )
      {
          this.fs = fs;
          this.out = out;
          this.locale = locale;
          this.bitmapCache = bitmapCache;
          this.stack = new Stack<>();
          this.uiConfig = uiConfig;
          this.frame = frame;
          this.sound = sound;
          this.menuPanel = new JPanel( new GridBagLayout() );
  
          LevelsList levelsList = LoadLevelsList.load(
              MenuDefinition.allLevels );
  
          this.levelsCompleted = new ByNameConfigBasedLevelsCompleted(
              uiConfig, levelsList );
  
          stack.push(
              MenuDefinition.mainMenu( this.levelsCompleted, levelsList, true )
          );
  
          init();
      }
  
      public void init()
      {
          Container contentPane = frame.getContentPane();
  
          contentPane.setLayout( new BorderLayout( 4, 4 ) );
  
          sidemenu = new SideMenu(
              contentPane,
              bitmapCache,
              new Dimension( 32, 32 ),
              uiConfig,
              backgroundColor
          );
  
          JScrollPane scrollPane = new JScrollPane( menuPanel  );
          contentPane.add( scrollPane, BorderLayout.CENTER );
          contentPane.setBackground( backgroundColor );
          scrollPane.setBackground( backgroundColor );
          scrollPane.getVerticalScrollBar().setUnitIncrement( 16 );
          menuPanel.setBackground( backgroundColor );
  
          placeMenu();
  
          frame.setBoundsFromConfig();
  
          frame.setTitle( t( "Rabbit Escape" ) );
  
          frame.pack();
          frame.setVisible( true );
  
          initListeners();
      }
  
      private void uninit()
      {
          frame.getContentPane().removeAll();
      }
  
      public void placeMenu()
      {
          Menu menu = stack.lastElement();
  
          menuPanel.removeAll();
  
          Dimension buttonSize = new Dimension( 300, 40 );
  
          JLabel label = new JLabel( t( menu.intro ) );
          label.setHorizontalAlignment( SwingConstants.CENTER );
          label.setForeground( Color.RED );
          label.setPreferredSize( buttonSize );
          menuPanel.add( label, constraints( 0 ) );
  
          for ( IdxObj<MenuItem> item : enumerate1( menu.items ) )
          {
              if ( item.object.hidden && !TapTimer.matched )
              {
                  continue;
              }
              JButton button = new JButton(
                  t( item.object.name, item.object.nameParams ) );
  
              button.setBackground( buttonColor );
              button.addActionListener(
                  new ButtonListener( item.object, menu ) );
              button.setVisible( true );
              button.setEnabled( item.object.enabled || TapTimer.matched );
              button.setPreferredSize( buttonSize );
              menuPanel.add( button, constraints( item.index ) );
          }
  
          sound.setMusic( "tryad-let_them_run" );
  
          frame.repaint();
          frame.revalidate();
      }
  
      public void refreshEnabledItems()
      {
          Menu menu = stack.lastElement();
          menu.refresh();
      }
  
      private GridBagConstraints constraints( int i )
      {
          return new GridBagConstraints(
              0,
              i,
              1,
              1,
              1.0,
              1.0,
              GridBagConstraints.CENTER,
              GridBagConstraints.NONE,
              new Insets( 10, 10, 10, 10 ),
              0,
              0
          );
      }
  
      private void chooseIssue()
      {
          GitHubIssueDialog id = new GitHubIssueDialog( frame );
          String world = id.getWorld();
          if( null == world )
          {
              return; // User clicked cancel, or selected issue with no world
          }
          String path = ConfigTools.getString(
              uiConfig, ConfigKeys.CFG_LOAD_LEVEL_PATH );
  
          File nameCandidate = new File ( path + File.separator +
                                          id.generateFilename() + ".rel" );
          int version = 0;
          String filename = id.generateFilename();
          while( nameCandidate.exists() )
          {
              nameCandidate = new File (
                  path + File.separator + filename + "." + ( version++ ) + ".rel"
              );
          }
          PrintWriter out;
          try
          {
              out = new PrintWriter( nameCandidate );
              out.print( world );
              out.close();
              playLevel( 
                  nameCandidate.getAbsolutePath(),
                  new IgnoreLevelWinListener(),
                  null,
                  null
              );
          }
          catch ( FileNotFoundException e ) /// @TODO fix exception handling
          {
              e.printStackTrace();
          }
      }
  
      private void chooseLevel()
      {
          String filename = chooseLevelFilename();
          if ( filename == null )
          {
              return;  // The user cancelled or closed the dialog
          }
  
          playLevel( filename, new IgnoreLevelWinListener(), null, null );
      }
  
      private String chooseLevelFilename()
      {
          String path = ConfigTools.getString(
              uiConfig, ConfigKeys.CFG_LOAD_LEVEL_PATH );
  
          final JFileChooser fc = new JFileChooser();
          final FileNameExtensionFilter relFilter = new FileNameExtensionFilter(
              t( "Rabbit Escape Level (*.rel)" ),
              "rel"
          );
          fc.setDialogTitle( t( "Open a level file" ) );
  
          fc.setCurrentDirectory( new File( path ) );
          fc.addChoosableFileFilter( relFilter );
          fc.setFileFilter( relFilter );
          int chooserVal = fc.showOpenDialog( frame );
  
          if ( JFileChooser.APPROVE_OPTION != chooserVal )
          {
              return null; // The user cancelled or closed the dialog
          }
  
          String filename = fc.getSelectedFile().getAbsolutePath();
  
          ConfigTools.setString(
              uiConfig,
              ConfigKeys.CFG_LOAD_LEVEL_PATH,
              fc.getCurrentDirectory().getAbsolutePath()
          );
          uiConfig.save();
  
          return filename;
      }
  
      private void level( final LevelMenuItem item, final LevelsMenu menu )
      {
          playLevel( item.fileName, winListeners( item ), item, menu );
      }
  
      private void playLevel(
          final String filename,
          final LevelWinListener levelWinListener,
          final LevelMenuItem item,
          final LevelsMenu menu )
      {
          new SwingWorker<Void, Void>()
          {
              @Override
              protected Void doInBackground() throws Exception
              {
                  uninit();
  
                  new SwingSingleGameEntryPoint(
                      fs,
                      out,
                      locale,
                      bitmapCache,
                      uiConfig,
                      frame,
                      sound,
                      MenuUi.this,
                      SwingGameLaunch.NOT_DEMO_MODE,
                      false,
                      item,
                      menu
                  ).launchGame(
                      new String[] { filename },
                      levelWinListener
                  );
  
                  return null;
              }
          }.execute();
      }
  
      protected LevelWinListener winListeners( LevelMenuItem item )
      {
          return new MultiLevelWinListener(
              new CompletedLevelWinListener(
                  item.levelsDir, item.levelNumber, levelsCompleted ),
              new UpdateSwingMenuLevelWinListener( this )
          );
      }
  
      private void about()
      {
          JTextPane text = new JTextPane();
          text.setText( t( AboutText.text ) );
          text.setBackground( null );
  
          JOptionPane.showMessageDialog(
              frame,
              text,
              t( "About Rabbit Escape" ),
              JOptionPane.INFORMATION_MESSAGE
          );
      }
  
      private void back()
      {
          stack.pop();
  
          if ( stack.empty() )
          {
              frame.exit();
          }
          else
          {
              SwingUtilities.invokeLater(
                  new Runnable()
                  {
                      @Override
                      public void run()
                      {
                          placeMenu();
                      }
                  }
              );
          }
      }
  
      private void setMuted( boolean muted )
      {
          ConfigTools.setBool( uiConfig, CFG_MUTED, muted );
          uiConfig.save();
          sound.mute( muted );
      }
  
      private void initListeners()
      {
          sidemenu.mute.addActionListener( new ActionListener()
          {
              @Override
              public void actionPerformed( ActionEvent evt )
              {
                  setMuted( sidemenu.mute.isSelected() );
              }
          } );
  
          MenuTools.clickOnKey( sidemenu.mute, "mute", KeyEvent.VK_M );
  
          sidemenu.back.addActionListener( new ActionListener()
          {
              @Override
              public void actionPerformed( ActionEvent e )
              {
                  back();
              }
          } );
  
          MenuTools.clickOnKey( sidemenu.back, "back", KeyEvent.VK_ESCAPE );
  
          sidemenu.exit.addActionListener( new ActionListener()
          {
              @Override
              public void actionPerformed( ActionEvent evt )
              {
                  frame.exit();
              }
          } );
  
          MenuTools.clickOnKey( sidemenu.exit, "quit", KeyEvent.VK_Q );
      }
  }
  