<commit_before>// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0

package com.google.appinventor.client;

import java.util.Random;

import java.util.List;
import java.util.logging.Logger;

import com.google.appinventor.client.boxes.AdminUserListBox;
import com.google.appinventor.client.boxes.AssetListBox;
import com.google.appinventor.client.boxes.BlockSelectorBox;
import com.google.appinventor.client.boxes.PrivateUserProfileTabPanel;
import com.google.appinventor.client.boxes.MessagesOutputBox;
import com.google.appinventor.client.boxes.OdeLogBox;
import com.google.appinventor.client.boxes.PaletteBox;
import com.google.appinventor.client.boxes.ProjectListBox;
import com.google.appinventor.client.boxes.ModerationPageBox;
import com.google.appinventor.client.boxes.GalleryListBox;
import com.google.appinventor.client.boxes.GalleryAppBox;
import com.google.appinventor.client.boxes.ProfileBox;
import com.google.appinventor.client.boxes.PropertiesBox;
import com.google.appinventor.client.boxes.SourceStructureBox;
import com.google.appinventor.client.boxes.ViewerBox;
import com.google.appinventor.client.editor.EditorManager;
import com.google.appinventor.client.editor.FileEditor;
import com.google.appinventor.client.editor.youngandroid.BlocklyPanel;
import com.google.appinventor.client.editor.youngandroid.TutorialPanel;
import com.google.appinventor.client.explorer.commands.ChainableCommand;
import com.google.appinventor.client.explorer.commands.CommandRegistry;
import com.google.appinventor.client.explorer.commands.SaveAllEditorsCommand;
import com.google.appinventor.client.explorer.project.Project;
import com.google.appinventor.client.explorer.project.ProjectChangeAdapter;
import com.google.appinventor.client.explorer.project.ProjectManager;
import com.google.appinventor.client.explorer.project.ProjectManagerEventAdapter;
import com.google.appinventor.client.explorer.youngandroid.GalleryList;
import com.google.appinventor.client.explorer.youngandroid.GalleryPage;
import com.google.appinventor.client.explorer.youngandroid.GalleryToolbar;
import com.google.appinventor.client.explorer.youngandroid.ProjectToolbar;
import com.google.appinventor.client.explorer.youngandroid.ReportList;
import com.google.appinventor.client.output.OdeLog;
import com.google.appinventor.client.settings.Settings;
import com.google.appinventor.client.settings.user.UserSettings;
import com.google.appinventor.client.tracking.Tracking;
import com.google.appinventor.client.utils.PZAwarePositionCallback;
import com.google.appinventor.client.widgets.boxes.Box;
import com.google.appinventor.client.widgets.boxes.ColumnLayout;
import com.google.appinventor.client.widgets.boxes.ColumnLayout.Column;
import com.google.appinventor.client.widgets.boxes.WorkAreaPanel;
import com.google.appinventor.client.wizards.NewProjectWizard.NewProjectCommand;
import com.google.appinventor.client.wizards.TemplateUploadWizard;
import com.google.appinventor.common.version.AppInventorFeatures;
import com.google.appinventor.components.common.YaVersion;
import com.google.appinventor.shared.rpc.component.ComponentService;
import com.google.appinventor.shared.rpc.component.ComponentServiceAsync;
import com.google.appinventor.shared.rpc.GetMotdService;
import com.google.appinventor.shared.rpc.GetMotdServiceAsync;
import com.google.appinventor.shared.rpc.RpcResult;
import com.google.appinventor.shared.rpc.ServerLayout;
import com.google.appinventor.shared.rpc.admin.AdminInfoService;
import com.google.appinventor.shared.rpc.admin.AdminInfoServiceAsync;
import com.google.appinventor.shared.rpc.help.HelpService;
import com.google.appinventor.shared.rpc.help.HelpServiceAsync;
import com.google.appinventor.shared.rpc.project.FileNode;
import com.google.appinventor.shared.rpc.project.GalleryAppListResult;
import com.google.appinventor.shared.rpc.project.GalleryComment;
import com.google.appinventor.shared.rpc.project.GallerySettings;
import com.google.appinventor.shared.rpc.project.ProjectRootNode;
import com.google.appinventor.shared.rpc.project.ProjectService;
import com.google.appinventor.shared.rpc.project.ProjectServiceAsync;
import com.google.appinventor.shared.rpc.project.UserProject;
import com.google.appinventor.shared.rpc.project.GalleryService;
import com.google.appinventor.shared.rpc.project.GalleryServiceAsync;
import com.google.appinventor.shared.rpc.project.youngandroid.YoungAndroidSourceNode;
import com.google.appinventor.shared.rpc.user.Config;
import com.google.appinventor.shared.rpc.user.SplashConfig;
import com.google.appinventor.shared.rpc.user.User;
import com.google.appinventor.shared.rpc.user.UserInfoService;
import com.google.appinventor.shared.rpc.user.UserInfoServiceAsync;
import com.google.appinventor.shared.settings.SettingsConstants;
import com.google.gwt.core.client.Callback;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseWheelEvent;
import com.google.gwt.event.dom.client.MouseWheelHandler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.http.client.Response;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.StatusCodeException;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.DeckPanel;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Frame;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PushButton;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.appinventor.shared.rpc.project.GalleryApp;

/**
 * Main entry point for Ode. Defines the startup UI elements in
 * {@link #onModuleLoad()}.
 *
 */
public class Ode implements EntryPoint {
  private static final Logger LOG = Logger.getLogger(Ode.class.getName());
  // I18n messages
  public static final OdeMessages MESSAGES = GWT.create(OdeMessages.class);

  // Global instance of the Ode object
  private static Ode instance;

  // Application level image bundle
  private static final Images IMAGES = GWT.create(Images.class);

  // ProjectEditor registry
  private static final ProjectEditorRegistry EDITORS = new ProjectEditorRegistry();

  // Command registry
  private static final CommandRegistry COMMANDS = new CommandRegistry();

  // System config
  private static Config config;

  // User settings
  private static UserSettings userSettings;

  // Gallery settings
  private static GallerySettings gallerySettings;

  private MotdFetcher motdFetcher;

  // User information
  private User user;

  // Template path if set by /?repo=
  private String templatePath;
  private boolean templateLoadingFlag = false;

  // Gallery id if set by /?galleryId=
  private String galleryId;
  private boolean galleryIdLoadingFlag = false;

  // Nonce Information
  private String nonce;

  // Read Only Flag: If true, the UI will not permit operations which permit
  // write requests

  private boolean isReadOnly;

  private String sessionId = generateUuid(); // Create new session id
  private Random random = new Random(); // For generating random nonce


  // Collection of projects
  private ProjectManager projectManager;

  // Collection of editors
  private EditorManager editorManager;

  // Currently active file editor, could be a YaFormEditor or a YaBlocksEditor or null.
  private FileEditor currentFileEditor;

  private AssetManager assetManager;

  // Remembers the current View
  static final int DESIGNER = 0;
  static final int PROJECTS = 1;
  private static final int GALLERY = 2;
  private static final int GALLERYAPP = 3;
  private static final int USERPROFILE = 4;
  private static final int PRIVATEUSERPROFILE = 5;
  private static final int MODERATIONPAGE = 6;
  private static final int USERADMIN = 7;
  private static int currentView = DESIGNER;

  /*
   * The following fields define the general layout of the UI as seen in the following diagram:
   *
   *  +-- mainPanel --------------------------------+
   *  |+-- topPanel -------------------------------+|
   *  ||                                           ||
   *  |+-------------------------------------------+|
   *  |+-- overDeckPanel --+-----------------------+|
   *  || tutorialPanel     |  deckPanel            ||
   *  |+-------------------+-----------------------+|
   *  |+-- statusPanel ----------------------------+|
   *  ||                                           ||
   *  |+-------------------------------------------+|
   *  +---------------------------------------------+
   */
  private DeckPanel deckPanel;
  private HorizontalPanel overDeckPanel;
  private Frame tutorialPanel;
  private int projectsTabIndex;
  private int designTabIndex;
  private int debuggingTabIndex;
  private int galleryTabIndex;
  private int galleryAppTabIndex;
  private int userAdminTabIndex;
  private int userProfileTabIndex;
  private int privateUserProfileIndex;
  private int moderationPageTabIndex;
  private TopPanel topPanel;
  private StatusPanel statusPanel;
  private HorizontalPanel workColumns;
  private VerticalPanel structureAndAssets;
  private ProjectToolbar projectToolbar;
  private GalleryToolbar galleryListToolbar;
  private GalleryToolbar galleryPageToolbar;
  private AdminUserListBox uaListBox;
  private DesignToolbar designToolbar;
  private TopToolbar topToolbar;

  // Is the tutorial toolbar currently displayed?
  private boolean tutorialVisible = false;

  // Popup that indicates that an asynchronous request is pending. It is visible
  // initially, and will be hidden automatically after the first RPC completes.
  private static RpcStatusPopup rpcStatusPopup;

  // Web service for help information
  private final HelpServiceAsync helpService = GWT.create(HelpService.class);

  // Web service for project related information
  private final ProjectServiceAsync projectService = GWT.create(ProjectService.class);

  // Web service for gallery related information
  private final GalleryServiceAsync galleryService = GWT.create(GalleryService.class);

  // Web service for user related information
  private final UserInfoServiceAsync userInfoService = GWT.create(UserInfoService.class);

  // Web service for get motd information
  private final GetMotdServiceAsync getMotdService = GWT.create(GetMotdService.class);

  // Web service for component related operations
  private final ComponentServiceAsync componentService = GWT.create(ComponentService.class);
  private final AdminInfoServiceAsync adminInfoService = GWT.create(AdminInfoService.class);

  private boolean windowClosing;

  private boolean screensLocked;

  private boolean galleryInitialized = false;

  /**
   * Flag set if we may need to show the splash screen based on
   * current user settings.
   */
  private boolean mayNeedSplash = false;

  /**
   * Flag to inidcate that we have already transitioned away from the
   * project list to a project (auto-open feature).
   */
  private boolean didTransitionFromProjectList = false;

  /**
   * Flag indicating that we have shown the splash screens.
   */
  private boolean didShowSplash = false;

  private SplashConfig splashConfig; // Splash Screen Configuration

  /**
   * Returns global instance of Ode.
   *
   * @return  global Ode instance
   */
  public static Ode getInstance() {
    return instance;
  }

  /**
   * Returns instance of the aggregate image bundle for the application.
   *
   * @return  image bundle
   */
  public static Images getImageBundle() {
    return IMAGES;
  }

  /**
   * Returns the editor registry.
   *
   * @return the editor registry
   */
  public static ProjectEditorRegistry getProjectEditorRegistry() {
    return EDITORS;
  }

  /**
   * Returns the command registry.
   *
   * @return the command registry
   */
  public static CommandRegistry getCommandRegistry() {
    return COMMANDS;
  }

  /**
   * Returns the system config.
   *
   * @return  system config
   */
  public static Config getSystemConfig() {
    return config;
  }

  /**
   * Returns the user settings.
   *
   * @return  user settings
   */
  public static UserSettings getUserSettings() {
    return userSettings;
  }

  /**
   * Returns the gallery settings.
   *
   * @return  gallery settings
   */
  public static GallerySettings getGallerySettings() {
    return gallerySettings;
  }

  /**
   * loads the gallery settings from server
   *
   */
  public void  loadGallerySettings() {
     // Callback for when the server returns us the apps
    final Ode ode = Ode.getInstance();
    final OdeAsyncCallback<GallerySettings> callback = new OdeAsyncCallback<GallerySettings>(
    // failure message
    MESSAGES.gallerySettingsError()) {
      @Override
      public void onSuccess(GallerySettings settings) {
        gallerySettings = settings;
        if(gallerySettings.galleryEnabled() == true){
          ProjectListBox.getProjectListBox().getProjectList().setPublishedHeaderVisible(true);
          projectToolbar.setPublishOrUpdateButtonVisible(true);
          GalleryClient.getInstance().setSystemEnvironment(settings.getEnvironment());
          topPanel.showGalleryLink(true);
          if(user.isModerator()){
            topPanel.showModerationLink(true);
          }
          topPanel.updateAccountMessageButton();
        }else{
          topPanel.showModerationLink(false);
          topPanel.showGalleryLink(false);
          projectToolbar.setPublishOrUpdateButtonVisible(false);
          ProjectListBox.getProjectListBox().getProjectList().setPublishedHeaderVisible(false);
        }
      }
    };
    //this is below the call back, but of course it is done first
    ode.getGalleryService().loadGallerySettings(callback);
  }

  /**
   * Returns the asset manager.
   *
   * @return  asset manager
   */
  public AssetManager getAssetManager() {
    return assetManager;
  }

  /**
   * Returns true if we have received the window closing event.
   */
  public static boolean isWindowClosing() {
    return getInstance().windowClosing;
  }

  /**
   * Get the current view
   */
  public int getCurrentView() {
    return currentView;
  }

  /**
   * Switch to the Projects tab
   */
  public void switchToProjectsView() {
    // We may need to pass the code below as a runnable to
    // screenShotMaybe() so build the runnable now
    hideTutorials();
    Runnable next = new Runnable() {
        @Override
        public void run() {
          if(currentView != PROJECTS) { //If we are switching to projects view from somewhere else, clear all of the previously selected projects.
            ProjectListBox.getProjectListBox().getProjectList().getSelectedProjects().clear();
            ProjectListBox.getProjectListBox().getProjectList().refreshTable(false);
          }
          currentView = PROJECTS;
          getTopToolbar().updateFileMenuButtons(currentView);
          deckPanel.showWidget(projectsTabIndex);
          // If we started a project, then the start button was disabled (to avoid
          // a second press while the new project wizard was starting (aka we "debounce"
          // the button). When the person switches to the projects list view again (here)
          // we re-enable it.
          projectToolbar.enableStartButton();
        }
      };
    if (designToolbar.getCurrentView() != DesignToolbar.View.BLOCKS) {
      next.run();
    } else {
      // maybe take a screenshot, second argument is true so we wait for i/o to complete
      screenShotMaybe(next, true);
    }
  }

  /**
   * Switch to the User Admin Panel
   */

  public void switchToUserAdminPanel() {
    hideTutorials();
    currentView = USERADMIN;
    deckPanel.showWidget(userAdminTabIndex);
  }

  /**
   * Switch to the Gallery tab
   */
  public void switchToGalleryView() {
    hideTutorials();
    if (!galleryInitialized) {
      // Gallery initialization is deferred until now.
      initializeGallery();
    }
    currentView = GALLERY;
    deckPanel.showWidget(galleryTabIndex);
  }

  /**
   * Switch to the Gallery App
   */
  public void switchToGalleryAppView(GalleryApp app, int editStatus) {
    hideTutorials();
    if (!galleryInitialized) {
      // Gallery initialization is deferred until now.
      initializeGallery();
    }
    currentView = GALLERYAPP;
    GalleryAppBox.setApp(app, editStatus);
    deckPanel.showWidget(galleryAppTabIndex);
  }

  /**
   * Switch to the user profile
   * TODO: change string parameter
   */
  public void switchToUserProfileView(String userId, int editStatus) {
    hideTutorials();
    currentView = USERPROFILE;
    OdeLog.log("###########" + userId + "||||||" + editStatus);
    ProfileBox.setProfile(userId, editStatus);
    deckPanel.showWidget(userProfileTabIndex);
  }

  /**
   * Switch to the Designer tab. Shows an error message if there is no currentFileEditor.
   */
  public void switchToDesignView() {
    // Only show designer if there is a current editor.
    // ***** THE DESIGNER TAB DOES NOT DISPLAY CORRECTLY IF THERE IS NO CURRENT EDITOR. *****
    showTutorials();
    currentView = DESIGNER;
    getTopToolbar().updateFileMenuButtons(currentView);
    if (currentFileEditor != null) {
      deckPanel.showWidget(designTabIndex);
    } else if (!editorManager.hasOpenEditor()) {  // is there a project editor pending visibility?
      OdeLog.wlog("No current file editor to show in designer");
      ErrorReporter.reportInfo(MESSAGES.chooseProject());
    }
  }

  /**
   * Switch to Gallery TabPanel
   */
  public void switchToPrivateUserProfileView() {
    currentView = privateUserProfileIndex;
    deckPanel.showWidget(privateUserProfileIndex);
  }

  /**
   * Switch to the Moderation Page tab
   */
  public void switchToModerationPageView() {
    hideTutorials();
    if (!galleryInitialized) {
      initializeGallery();
    }
    currentView = MODERATIONPAGE;
    deckPanel.showWidget(moderationPageTabIndex);
  }
  /**
   * Switch to the Debugging tab
   */
  public void switchToDebuggingView() {
    hideTutorials();
    deckPanel.showWidget(debuggingTabIndex);

    // NOTE(lizlooney) - Calling resizeWorkArea for debuggingTab prevents the
    // boxes from overlapping each other.
    resizeWorkArea((WorkAreaPanel) deckPanel.getWidget(debuggingTabIndex));
  }

  public void openPreviousProject() {
    if (userSettings == null) {
      OdeLog.wlog("Ignoring openPreviousProject() since userSettings is null");
      return;
    }
    OdeLog.log("Ode.openPreviousProject called");
    final String value = userSettings.getSettings(SettingsConstants.USER_GENERAL_SETTINGS).
      getPropertyValue(SettingsConstants.GENERAL_SETTINGS_CURRENT_PROJECT_ID);

    // Retrieve the userTemplates
    String userTemplates = userSettings.getSettings(SettingsConstants.USER_GENERAL_SETTINGS).
      getPropertyValue(SettingsConstants.USER_TEMPLATE_URLS);
    TemplateUploadWizard.setStoredTemplateUrls(userTemplates);

    if (templateLoadingFlag) {  // We are loading a template, open it instead
                                // of the last project
      NewProjectCommand callbackCommand = new NewProjectCommand() {
          @Override
          public void execute(Project project) {
            templateLoadingFlag = false;
            Ode.getInstance().openYoungAndroidProjectInDesigner(project);
          }
        };
      TemplateUploadWizard.openProjectFromTemplate(templatePath, callbackCommand);
    } else if(galleryIdLoadingFlag){
      try {
        long galleryId_Long = Long.valueOf(galleryId);
        final OdeAsyncCallback<GalleryApp> callback = new OdeAsyncCallback<GalleryApp>(
            // failure message
            MESSAGES.galleryError()) {
              @Override
              public void onSuccess(GalleryApp app) {
                if(app == null){
                  openProject(value);
                  Window.alert(MESSAGES.galleryIdNotExist());
                }else{
                  Ode.getInstance().switchToGalleryAppView(app, GalleryPage.VIEWAPP);
                }
              }
            };
        Ode.getInstance().getGalleryService().getApp(galleryId_Long, callback);
      } catch (NumberFormatException e) {
        openProject(value);
        Window.alert(MESSAGES.galleryIdNotExist());
      }
    } else {
      openProject(value);
    }
  }

  private void openProject(String projectIdString) {
    OdeLog.log("Ode.openProject called for " + projectIdString);
    if (projectIdString.equals("")) {
      openPreviousProject();
    } else if (!projectIdString.equals("0")) {
      final long projectId = Long.parseLong(projectIdString);
      Project project = projectManager.getProject(projectId);
      if (project != null) {
        openYoungAndroidProjectInDesigner(project);
      } else {
        // The project hasn't been added to the ProjectManager yet.
        // Add a ProjectManagerEventListener so we'll be notified when it has been added.
        // Alternatively, it is an invalid projectId. In which case,
        // nothing happens since if the listener eventually fires
        // it will not match the projectId.
        projectManager.addProjectManagerEventListener(new ProjectManagerEventAdapter() {
          @Override
          public void onProjectAdded(Project project) {
            if (project.getProjectId() == projectId) {
              projectManager.removeProjectManagerEventListener(this);
              openYoungAndroidProjectInDesigner(project);
            }
          }
          @Override
          public void onProjectsLoaded() {
            // we only get here iff onProjectAdded is never called with the target project id
            projectManager.removeProjectManagerEventListener(this);
            switchToProjectsView();  // the user will need to select a project...
            ErrorReporter.reportInfo(MESSAGES.chooseProject());
          }
        });
      }
    }
    // else projectIdString == 0; do nothing
  }

  public void openYoungAndroidProjectInDesigner(final Project project) {
    ProjectRootNode projectRootNode = project.getRootNode();
    if (projectRootNode == null) {
      // The project nodes haven't been loaded yet.
      // Add a ProjectChangeListener so we'll be notified when they have been loaded.
      project.addProjectChangeListener(new ProjectChangeAdapter() {
        @Override
        public void onProjectLoaded(Project projectLoaded) {
          project.removeProjectChangeListener(this);
          openYoungAndroidProjectInDesigner(project);
        }
      });
      project.loadProjectNodes();

    } else {
      // The project nodes have been loaded. Tell the viewer to open
      // the project. This will cause the projects source files to be fetched
      // asynchronously, and loaded into file editors.
      ViewerBox.getViewerBox().show(projectRootNode);
      // Note: we can't call switchToDesignView until the Screen1 file editor
      // finishes loading. We leave that to setCurrentFileEditor(), which
      // will get called at the appropriate time.
      String projectIdString = Long.toString(project.getProjectId());
      if (!History.getToken().equals(projectIdString)) {
        // insert token into history but do not trigger listener event
        History.newItem(projectIdString, false);
      }
      if (assetManager == null) {
        assetManager = AssetManager.getInstance();
      }
      assetManager.loadAssets(project.getProjectId());
    }
    getTopToolbar().updateFileMenuButtons(1);
  }

  /**
   * Returns i18n compatible messages
   * @return messages
   */
  public static OdeMessages getMessages() {
    return MESSAGES;
  }

  /**
   * Returns the rpcStatusPopup object.
   * @return RpcStatusPopup
   */
  public static RpcStatusPopup getRpcStatusPopup() {
    return rpcStatusPopup;
  }

  /**
   * Main entry point for Ode. Setting up the UI and the web service
   * connections.
   */
  @Override
  public void onModuleLoad() {
    Tracking.trackPageview();

    // Handler for any otherwise unhandled exceptions
    GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler() {
      @Override
      public void onUncaughtException(Throwable e) {
        OdeLog.xlog(e);

        if (AppInventorFeatures.sendBugReports()) {
          if (Window.confirm(MESSAGES.internalErrorReportBug())) {
            Window.open(BugReport.getBugReportLink(e), "_blank", "");
          }
        } else {
          // Display a confirm dialog with error msg and if 'ok' open the debugging view
          if (Window.confirm(MESSAGES.internalErrorClickOkDebuggingView())) {
            Ode.getInstance().switchToDebuggingView();
          }
        }
      }
    });

    // Initialize global Ode instance
    instance = this;

    // Let's see if we were started with a repo= parameter which points to a template
    templatePath = Window.Location.getParameter("repo");
    if (templatePath != null) {
      OdeLog.wlog("Got a template path of " + templatePath);
      templateLoadingFlag = true;
    }

    // Let's see if we were started with a galleryId= parameter which points to a template
    galleryId = Window.Location.getParameter("galleryId");
    if(galleryId != null){
      OdeLog.wlog("Got a galleryId of " + galleryId);
      galleryIdLoadingFlag = true;
    }

    // Get user information.
    OdeAsyncCallback<Config> callback = new OdeAsyncCallback<Config>(
        // failure message
        MESSAGES.serverUnavailable()) {

      @Override
      public void onSuccess(Config result) {
        config = result;
        user = result.getUser();
        isReadOnly = user.isReadOnly();

        // Setup noop timer (if enabled)
        int noop = config.getNoop();
        if (noop > 0) {
          // If we have a noop time, setup a timer to do the noop
          Timer t = new Timer() {
              @Override
              public void run() {
                userInfoService.noop(new AsyncCallback<Void>() {
                    @Override
                    public void onSuccess(Void e) {
                    }
                    @Override
                    public void onFailure(Throwable e) {
                    }
                  });
              }
            };
            t.scheduleRepeating(1000*60*noop);
        }

        // If user hasn't accepted terms of service, ask them to.
        if (!user.getUserTosAccepted() && !isReadOnly) {
          // We expect that the redirect to the TOS page should be handled
          // by the onFailure method below. The server should return a
          // "forbidden" error if the TOS wasn't accepted.
          ErrorReporter.reportError(MESSAGES.serverUnavailable());
          return;
        }

        splashConfig = result.getSplashConfig();

        if (result.getRendezvousServer() != null) {
          setRendezvousServer(result.getRendezvousServer());
        } else {
          setRendezvousServer(YaVersion.RENDEZVOUS_SERVER);
        }

        userSettings = new UserSettings(user);

        // Gallery settings
        gallerySettings = new GallerySettings();
        //gallerySettings.loadGallerySettings();
        loadGallerySettings();

        // Initialize project and editor managers
        // The project manager loads the user's projects asynchronously
        projectManager = new ProjectManager();
        projectManager.addProjectManagerEventListener(new ProjectManagerEventAdapter() {
          @Override
          public void onProjectsLoaded() {
            projectManager.removeProjectManagerEventListener(this);

            // This handles any built-in templates stored in /war
            // Retrieve template data stored in war/templates folder and
            // and save it for later use in TemplateUploadWizard
            OdeAsyncCallback<String> templateCallback =
                new OdeAsyncCallback<String>(
                  // failure message
                  MESSAGES.createProjectError()) {
                  @Override
                  public void onSuccess(String json) {
                    // Save the templateData
                    TemplateUploadWizard.initializeBuiltInTemplates(json);
                    // Here we call userSettings.loadSettings, but the settings are actually loaded
                    // asynchronously, so this loadSettings call will return before they are loaded.
                    // After the user settings have been loaded, openPreviousProject will be called.
                    // We have to call this after the builtin templates have been loaded otherwise
                    // we will get a NPF.
                    userSettings.loadSettings();
                  }
                };
            Ode.getInstance().getProjectService().retrieveTemplateData(TemplateUploadWizard.TEMPLATES_ROOT_DIRECTORY, templateCallback);
          }
        });
        editorManager = new EditorManager();

        // Initialize UI
        initializeUi();

        topPanel.showUserEmail(user.getUserEmail());
      }

      @Override
      public void onFailure(Throwable caught) {
        if (caught instanceof StatusCodeException) {
          StatusCodeException e = (StatusCodeException) caught;
          int statusCode = e.getStatusCode();
          switch (statusCode) {
            case Response.SC_UNAUTHORIZED:
              // unauthorized => not on whitelist
              // getEncodedResponse() gives us the message that we wrote in
              // OdeAuthFilter.writeWhitelistErrorMessage().
              Window.alert(e.getEncodedResponse());
              return;
            case Response.SC_FORBIDDEN:
              // forbidden => need tos accept
              Window.open("/" + ServerLayout.YA_TOS_FORM, "_self", null);
              return;
            case Response.SC_PRECONDITION_FAILED:
              String locale = Window.Location.getParameter("locale");
              String repo = Window.Location.getParameter("repo");
              galleryId = Window.Location.getParameter("galleryId");
              String separator = "?";
              String uri = "/login/";
              if (locale != null && !locale.equals("")) {
                uri += separator + "locale=" + locale;
                separator = "&";
              }
              if (repo != null && !repo.equals("")) {
                uri += separator + "repo=" + repo;
                separator = "&";
              }
              if (galleryId != null && !galleryId.equals("")) {
                uri += separator + "galleryId=" + galleryId;
              }
              Window.Location.replace(uri);
              return;           // likely not reached
          }
        }
        super.onFailure(caught);
      }
    };

    // The call below begins an asynchronous read of the user's settings
    // When the settings are finished reading, various settings parsers
    // will be called on the returned JSON object. They will call various
    // other functions in this module, including openPreviousProject (the
    // previous project ID is stored in the settings) as well as the splash
    // screen displaying functions below.
    //
    // TODO(user): ODE makes too many RPC requests at startup time. Currently
    // we do 3 RPCs + 1 per project + 1 per open file. We should bundle some of
    // those with each other or with the initial HTML transfer.
    //
    // This call also stores our sessionId in the backend. This will be checked
    // when we go to save a file and if different file saving will be disabled
    // Newer sessions invalidate older sessions.

    userInfoService.getSystemConfig(sessionId, callback);

    // We fetch the user's backpack here. This runs asynchronously with the rest
    // of the system initialization.

    userInfoService.getUserBackpack(new AsyncCallback<String>() {
        @Override
        public void onSuccess(String backpack) {
          BlocklyPanel.setInitialBackpack(backpack);
        }
        @Override
        public void onFailure(Throwable caught) {
          OdeLog.log("Fetching backpack failed");
        }
      });

    History.addValueChangeHandler(new ValueChangeHandler<String>() {
      @Override
      public void onValueChange(ValueChangeEvent<String> event) {
        openProject(event.getValue());
      }
    });

    // load project based on current url
    // TODO(sharon): Seems like a possible race condition here if the onValueChange
    // handler defined above gets called before the getSystemConfig call sets
    // userSettings.
    // The following line causes problems with GWT debugging, and commenting
    // it out doesn't seem to break things.
    //History.fireCurrentHistoryState();
  }

  /*
   * Initializes all UI elements.
   */
  private void initializeUi() {
    rpcStatusPopup = new RpcStatusPopup();

    // Register services with RPC status popup
    rpcStatusPopup.register((ExtendedServiceProxy<?>) helpService);
    rpcStatusPopup.register((ExtendedServiceProxy<?>) projectService);
    rpcStatusPopup.register((ExtendedServiceProxy<?>) galleryService);
    rpcStatusPopup.register((ExtendedServiceProxy<?>) userInfoService);

    Window.setTitle(MESSAGES.titleYoungAndroid());
    Window.enableScrolling(true);

    topPanel = new TopPanel();
    statusPanel = new StatusPanel();

    DockPanel mainPanel = new DockPanel();
    mainPanel.add(topPanel, DockPanel.NORTH);

    // Create the Tutorial Panel
    tutorialPanel = new TutorialPanel();
    tutorialPanel.setWidth("100%");
    tutorialPanel.setHeight("100%");
    // Initially we do not display it. If the project we load has
    // a tutorial URL, then we will set this visible when we load
    // the project
    tutorialPanel.setVisible(false);

    // Create tab panel for subsequent tabs
    deckPanel = new DeckPanel() {
      @Override
      public final void onBrowserEvent(Event event) {
        switch (event.getTypeInt()) {
          case Event.ONCONTEXTMENU:
            event.preventDefault();
            break;
        }
      }
    };

    deckPanel.setAnimationEnabled(true);
    deckPanel.sinkEvents(Event.ONCONTEXTMENU);
    deckPanel.setStyleName("ode-DeckPanel");

    // Projects tab
    VerticalPanel pVertPanel = new VerticalPanel() {
        /**
         * Flag to indicate the project list has been rendered at least once.
         */
        private boolean rendered = false;

        // Override to add splash screen behavior after leaving the project list
        @Override
        public void setVisible(boolean visible) {
          super.setVisible(visible);
          if (visible && !rendered) {
            // setVisible(false) will be called during UI initialization.
            // this flag indicates we are now being shown (possibly again...)
            rendered = true;
            maybeShowSplash2();  // in case of new user; they have no projects!
          } else if (rendered && !visible && (mayNeedSplash || shouldShowWelcomeDialog())
                     && !didShowSplash) {
            showSplashScreens();
          }
        }
      };
    pVertPanel.setWidth("100%");
    pVertPanel.setSpacing(0);
    HorizontalPanel projectListPanel = new HorizontalPanel();
    projectListPanel.setWidth("100%");
    projectToolbar = new ProjectToolbar();
    projectListPanel.add(ProjectListBox.getProjectListBox());
    pVertPanel.add(projectToolbar);
    pVertPanel.add(projectListPanel);
    projectsTabIndex = deckPanel.getWidgetCount();
    deckPanel.add(pVertPanel);

    // Design tab
    VerticalPanel dVertPanel = new VerticalPanel();
    dVertPanel.setWidth("100%");
    dVertPanel.setHeight("100%");

    // Add the Code Navigation arrow
//    switchToBlocksButton = new VerticalPanel();
//    switchToBlocksButton.setVerticalAlignment(VerticalPanel.ALIGN_MIDDLE);
//    switchToBlocksButton.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
//    switchToBlocksButton.setStyleName("ode-NavArrow");
//    switchToBlocksButton.add(new Image(RIGHT_ARROW_IMAGE_URL));
//    switchToBlocksButton.setWidth("25px");
//    switchToBlocksButton.setHeight("100%");

    // Add the Code Navigation arrow
//    switchToDesignerButton = new VerticalPanel();
//    switchToDesignerButton.setVerticalAlignment(VerticalPanel.ALIGN_MIDDLE);
//    switchToDesignerButton.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
//    switchToDesignerButton.setStyleName("ode-NavArrow");
//    switchToDesignerButton.add(new Image(LEFT_ARROW_IMAGE_URL));
//    switchToDesignerButton.setWidth("25px");
//    switchToDesignerButton.setHeight("100%");

    designToolbar = new DesignToolbar();
    dVertPanel.add(designToolbar);

    workColumns = new HorizontalPanel();
    workColumns.setWidth("100%");

    //workColumns.add(switchToDesignerButton);

    Box palletebox = PaletteBox.getPaletteBox();
    palletebox.setWidth("240px");
    workColumns.add(palletebox);

    Box viewerbox = ViewerBox.getViewerBox();
    workColumns.add(viewerbox);
    workColumns.setCellWidth(viewerbox, "97%");
    workColumns.setCellHeight(viewerbox, "97%");

    structureAndAssets = new VerticalPanel();
    structureAndAssets.setVerticalAlignment(VerticalPanel.ALIGN_TOP);
    // Only one of the SourceStructureBox and the BlockSelectorBox is visible
    // at any given time, according to whether we are showing the form editor
    // or the blocks editor. They share the same screen real estate.
    structureAndAssets.add(SourceStructureBox.getSourceStructureBox());
    structureAndAssets.add(BlockSelectorBox.getBlockSelectorBox());  // initially not visible
    structureAndAssets.add(AssetListBox.getAssetListBox());
    workColumns.add(structureAndAssets);

    Box propertiesbox = PropertiesBox.getPropertiesBox();
    propertiesbox.setWidth("222px");
    workColumns.add(propertiesbox);
    //switchToBlocksButton.setHeight("650px");
    //workColumns.add(switchToBlocksButton);
    dVertPanel.add(workColumns);
    designTabIndex = deckPanel.getWidgetCount();
    deckPanel.add(dVertPanel);

    // Gallery list tab
    VerticalPanel gVertPanel = new VerticalPanel();
    gVertPanel.add(createLoadingWidget(GalleryList.INITIAL_RPCS));
    galleryTabIndex = deckPanel.getWidgetCount();
    deckPanel.add(gVertPanel);

     // Gallery app tab
    VerticalPanel aVertPanel = new VerticalPanel();
    galleryAppTabIndex = deckPanel.getWidgetCount();
    deckPanel.add(aVertPanel);

    // User Admin Panel
    VerticalPanel uaVertPanel = new VerticalPanel();
    uaVertPanel.setWidth("100%");
    uaVertPanel.setSpacing(0);
    HorizontalPanel adminUserListPanel = new HorizontalPanel();
    adminUserListPanel.setWidth("100%");
    adminUserListPanel.add(AdminUserListBox.getAdminUserListBox());
    uaVertPanel.add(adminUserListPanel);
    userAdminTabIndex = deckPanel.getWidgetCount();
    deckPanel.add(uaVertPanel);

    // KM: DEBUGGING BEGIN
    // User profile tab
    VerticalPanel uVertPanel = new VerticalPanel();
    uVertPanel.setWidth("100%");
    uVertPanel.setSpacing(0);
    HorizontalPanel userProfilePanel = new HorizontalPanel();
    userProfilePanel.setWidth("100%");
    userProfilePanel.add(ProfileBox.getUserProfileBox());

    uVertPanel.add(userProfilePanel);
    userProfileTabIndex = deckPanel.getWidgetCount();
    deckPanel.add(uVertPanel);
    // KM: DEBUGGING END

    // Private User Profile TabPanel
    VerticalPanel ppVertPanel = new VerticalPanel();
    ppVertPanel.setWidth("100%");
    ppVertPanel.setSpacing(0);
    HorizontalPanel privateUserProfileTabPanel = new HorizontalPanel();
    privateUserProfileTabPanel.setWidth("100%");
    privateUserProfileTabPanel.add(PrivateUserProfileTabPanel.getPrivateUserProfileTabPanel());
    ppVertPanel.add(privateUserProfileTabPanel);
    privateUserProfileIndex = deckPanel.getWidgetCount();
    deckPanel.add(ppVertPanel);

    // Moderation Page tab
    VerticalPanel mPVertPanel = new VerticalPanel();
    mPVertPanel.add(createLoadingWidget(ReportList.INITIAL_RPCS));
    moderationPageTabIndex = deckPanel.getWidgetCount();
    deckPanel.add(mPVertPanel);

    // Debugging tab
    if (AppInventorFeatures.hasDebuggingView()) {

      Button dismissButton = new Button(MESSAGES.dismissButton());
      dismissButton.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
          if (currentView == DESIGNER)
            switchToDesignView();
          else
            switchToProjectsView();
        }
      });

      ColumnLayout defaultLayout = new ColumnLayout("Default");
      Column column = defaultLayout.addColumn(100);
      column.add(MessagesOutputBox.class, 300, false);
      column.add(OdeLogBox.class, 300, false);
      final WorkAreaPanel debuggingTab = new WorkAreaPanel(new OdeBoxRegistry(), defaultLayout);

      debuggingTab.add(dismissButton);

      debuggingTabIndex = deckPanel.getWidgetCount();
      deckPanel.add(debuggingTab);

      // Hook the window resize event, so that we can adjust the UI.
      Window.addResizeHandler(new ResizeHandler() {
        @Override
        public void onResize(ResizeEvent event) {
          resizeWorkArea(debuggingTab);
        }
      });

      // Call the window resized handler to get the initial sizes setup. Doing this in a deferred
      // command causes it to occur after all widgets' sizes have been computed by the browser.
      DeferredCommand.addCommand(new Command() {
        @Override
        public void execute() {
          resizeWorkArea(debuggingTab);
        }
      });

      resizeWorkArea(debuggingTab);
    }

    // We do not select the designer tab here because at this point there is no current project.
    // Instead, we select the projects tab. If the user has a previously opened project, we will
    // open it and switch to the designer after the user settings are loaded.
    // Remember, the user may not have any projects at all yet.
    // Or, the user may have deleted their previously opened project.
    // ***** THE DESIGNER TAB DOES NOT DISPLAY CORRECTLY IF THERE IS NO CURRENT PROJECT. *****
    deckPanel.showWidget(projectsTabIndex);

    overDeckPanel = new HorizontalPanel();
    overDeckPanel.setHeight("100%");
    overDeckPanel.setWidth("100%");
    overDeckPanel.add(tutorialPanel);
    overDeckPanel.setCellWidth(tutorialPanel, "0%");
    overDeckPanel.add(deckPanel);
    mainPanel.add(overDeckPanel, DockPanel.CENTER);
    mainPanel.setCellHeight(overDeckPanel, "100%");
    mainPanel.setCellWidth(overDeckPanel, "100%");

//    mainPanel.add(switchToDesignerButton, DockPanel.WEST);
//    mainPanel.add(switchToBlocksButton, DockPanel.EAST);

    //Commenting out for now to gain more space for the blocks editor
    mainPanel.add(statusPanel, DockPanel.SOUTH);
    mainPanel.setSize("100%", "100%");
    RootPanel.get().add(mainPanel);

    // Add a handler to the RootPanel to keep track of Google Chrome Pinch Zooming and
    // handle relevant bugs. Chrome maps a Pinch Zoom to a MouseWheelEvent with the
    // control key pressed.
    RootPanel.get().addDomHandler(new MouseWheelHandler() {
      @Override
      public void onMouseWheel(MouseWheelEvent event) {
        if(event.isControlKeyDown()) {
          // Trip the appropriate flag in PZAwarePositionCallback when the page
          // is Pinch Zoomed. Note that this flag does not need to be removed when
          // the browser is un-zoomed because the patched function for determining
          // absolute position works in all circumstances.
          PZAwarePositionCallback.setPinchZoomed(true);<commit_msg>Fix Tutorial Panel height on Firefox<commit_after>// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0

package com.google.appinventor.client;

import java.util.Random;

import java.util.List;
import java.util.logging.Logger;

import com.google.appinventor.client.boxes.AdminUserListBox;
import com.google.appinventor.client.boxes.AssetListBox;
import com.google.appinventor.client.boxes.BlockSelectorBox;
import com.google.appinventor.client.boxes.PrivateUserProfileTabPanel;
import com.google.appinventor.client.boxes.MessagesOutputBox;
import com.google.appinventor.client.boxes.OdeLogBox;
import com.google.appinventor.client.boxes.PaletteBox;
import com.google.appinventor.client.boxes.ProjectListBox;
import com.google.appinventor.client.boxes.ModerationPageBox;
import com.google.appinventor.client.boxes.GalleryListBox;
import com.google.appinventor.client.boxes.GalleryAppBox;
import com.google.appinventor.client.boxes.ProfileBox;
import com.google.appinventor.client.boxes.PropertiesBox;
import com.google.appinventor.client.boxes.SourceStructureBox;
import com.google.appinventor.client.boxes.ViewerBox;
import com.google.appinventor.client.editor.EditorManager;
import com.google.appinventor.client.editor.FileEditor;
import com.google.appinventor.client.editor.youngandroid.BlocklyPanel;
import com.google.appinventor.client.editor.youngandroid.TutorialPanel;
import com.google.appinventor.client.explorer.commands.ChainableCommand;
import com.google.appinventor.client.explorer.commands.CommandRegistry;
import com.google.appinventor.client.explorer.commands.SaveAllEditorsCommand;
import com.google.appinventor.client.explorer.project.Project;
import com.google.appinventor.client.explorer.project.ProjectChangeAdapter;
import com.google.appinventor.client.explorer.project.ProjectManager;
import com.google.appinventor.client.explorer.project.ProjectManagerEventAdapter;
import com.google.appinventor.client.explorer.youngandroid.GalleryList;
import com.google.appinventor.client.explorer.youngandroid.GalleryPage;
import com.google.appinventor.client.explorer.youngandroid.GalleryToolbar;
import com.google.appinventor.client.explorer.youngandroid.ProjectToolbar;
import com.google.appinventor.client.explorer.youngandroid.ReportList;
import com.google.appinventor.client.output.OdeLog;
import com.google.appinventor.client.settings.Settings;
import com.google.appinventor.client.settings.user.UserSettings;
import com.google.appinventor.client.tracking.Tracking;
import com.google.appinventor.client.utils.PZAwarePositionCallback;
import com.google.appinventor.client.widgets.boxes.Box;
import com.google.appinventor.client.widgets.boxes.ColumnLayout;
import com.google.appinventor.client.widgets.boxes.ColumnLayout.Column;
import com.google.appinventor.client.widgets.boxes.WorkAreaPanel;
import com.google.appinventor.client.wizards.NewProjectWizard.NewProjectCommand;
import com.google.appinventor.client.wizards.TemplateUploadWizard;
import com.google.appinventor.common.version.AppInventorFeatures;
import com.google.appinventor.components.common.YaVersion;
import com.google.appinventor.shared.rpc.component.ComponentService;
import com.google.appinventor.shared.rpc.component.ComponentServiceAsync;
import com.google.appinventor.shared.rpc.GetMotdService;
import com.google.appinventor.shared.rpc.GetMotdServiceAsync;
import com.google.appinventor.shared.rpc.RpcResult;
import com.google.appinventor.shared.rpc.ServerLayout;
import com.google.appinventor.shared.rpc.admin.AdminInfoService;
import com.google.appinventor.shared.rpc.admin.AdminInfoServiceAsync;
import com.google.appinventor.shared.rpc.help.HelpService;
import com.google.appinventor.shared.rpc.help.HelpServiceAsync;
import com.google.appinventor.shared.rpc.project.FileNode;
import com.google.appinventor.shared.rpc.project.GalleryAppListResult;
import com.google.appinventor.shared.rpc.project.GalleryComment;
import com.google.appinventor.shared.rpc.project.GallerySettings;
import com.google.appinventor.shared.rpc.project.ProjectRootNode;
import com.google.appinventor.shared.rpc.project.ProjectService;
import com.google.appinventor.shared.rpc.project.ProjectServiceAsync;
import com.google.appinventor.shared.rpc.project.UserProject;
import com.google.appinventor.shared.rpc.project.GalleryService;
import com.google.appinventor.shared.rpc.project.GalleryServiceAsync;
import com.google.appinventor.shared.rpc.project.youngandroid.YoungAndroidSourceNode;
import com.google.appinventor.shared.rpc.user.Config;
import com.google.appinventor.shared.rpc.user.SplashConfig;
import com.google.appinventor.shared.rpc.user.User;
import com.google.appinventor.shared.rpc.user.UserInfoService;
import com.google.appinventor.shared.rpc.user.UserInfoServiceAsync;
import com.google.appinventor.shared.settings.SettingsConstants;
import com.google.gwt.core.client.Callback;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseWheelEvent;
import com.google.gwt.event.dom.client.MouseWheelHandler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.http.client.Response;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.StatusCodeException;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.DeckPanel;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Frame;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PushButton;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.appinventor.shared.rpc.project.GalleryApp;

/**
 * Main entry point for Ode. Defines the startup UI elements in
 * {@link #onModuleLoad()}.
 *
 */
public class Ode implements EntryPoint {
  private static final Logger LOG = Logger.getLogger(Ode.class.getName());
  // I18n messages
  public static final OdeMessages MESSAGES = GWT.create(OdeMessages.class);

  // Global instance of the Ode object
  private static Ode instance;

  // Application level image bundle
  private static final Images IMAGES = GWT.create(Images.class);

  // ProjectEditor registry
  private static final ProjectEditorRegistry EDITORS = new ProjectEditorRegistry();

  // Command registry
  private static final CommandRegistry COMMANDS = new CommandRegistry();

  // System config
  private static Config config;

  // User settings
  private static UserSettings userSettings;

  // Gallery settings
  private static GallerySettings gallerySettings;

  private MotdFetcher motdFetcher;

  // User information
  private User user;

  // Template path if set by /?repo=
  private String templatePath;
  private boolean templateLoadingFlag = false;

  // Gallery id if set by /?galleryId=
  private String galleryId;
  private boolean galleryIdLoadingFlag = false;

  // Nonce Information
  private String nonce;

  // Read Only Flag: If true, the UI will not permit operations which permit
  // write requests

  private boolean isReadOnly;

  private String sessionId = generateUuid(); // Create new session id
  private Random random = new Random(); // For generating random nonce


  // Collection of projects
  private ProjectManager projectManager;

  // Collection of editors
  private EditorManager editorManager;

  // Currently active file editor, could be a YaFormEditor or a YaBlocksEditor or null.
  private FileEditor currentFileEditor;

  private AssetManager assetManager;

  // Remembers the current View
  static final int DESIGNER = 0;
  static final int PROJECTS = 1;
  private static final int GALLERY = 2;
  private static final int GALLERYAPP = 3;
  private static final int USERPROFILE = 4;
  private static final int PRIVATEUSERPROFILE = 5;
  private static final int MODERATIONPAGE = 6;
  private static final int USERADMIN = 7;
  private static int currentView = DESIGNER;

  /*
   * The following fields define the general layout of the UI as seen in the following diagram:
   *
   *  +-- mainPanel --------------------------------+
   *  |+-- topPanel -------------------------------+|
   *  ||                                           ||
   *  |+-------------------------------------------+|
   *  |+-- overDeckPanel --+-----------------------+|
   *  || tutorialPanel     |  deckPanel            ||
   *  |+-------------------+-----------------------+|
   *  |+-- statusPanel ----------------------------+|
   *  ||                                           ||
   *  |+-------------------------------------------+|
   *  +---------------------------------------------+
   */
  private DeckPanel deckPanel;
  private HorizontalPanel overDeckPanel;
  private Frame tutorialPanel;
  private int projectsTabIndex;
  private int designTabIndex;
  private int debuggingTabIndex;
  private int galleryTabIndex;
  private int galleryAppTabIndex;
  private int userAdminTabIndex;
  private int userProfileTabIndex;
  private int privateUserProfileIndex;
  private int moderationPageTabIndex;
  private TopPanel topPanel;
  private StatusPanel statusPanel;
  private HorizontalPanel workColumns;
  private VerticalPanel structureAndAssets;
  private ProjectToolbar projectToolbar;
  private GalleryToolbar galleryListToolbar;
  private GalleryToolbar galleryPageToolbar;
  private AdminUserListBox uaListBox;
  private DesignToolbar designToolbar;
  private TopToolbar topToolbar;

  // Is the tutorial toolbar currently displayed?
  private boolean tutorialVisible = false;

  // Popup that indicates that an asynchronous request is pending. It is visible
  // initially, and will be hidden automatically after the first RPC completes.
  private static RpcStatusPopup rpcStatusPopup;

  // Web service for help information
  private final HelpServiceAsync helpService = GWT.create(HelpService.class);

  // Web service for project related information
  private final ProjectServiceAsync projectService = GWT.create(ProjectService.class);

  // Web service for gallery related information
  private final GalleryServiceAsync galleryService = GWT.create(GalleryService.class);

  // Web service for user related information
  private final UserInfoServiceAsync userInfoService = GWT.create(UserInfoService.class);

  // Web service for get motd information
  private final GetMotdServiceAsync getMotdService = GWT.create(GetMotdService.class);

  // Web service for component related operations
  private final ComponentServiceAsync componentService = GWT.create(ComponentService.class);
  private final AdminInfoServiceAsync adminInfoService = GWT.create(AdminInfoService.class);

  private boolean windowClosing;

  private boolean screensLocked;

  private boolean galleryInitialized = false;

  /**
   * Flag set if we may need to show the splash screen based on
   * current user settings.
   */
  private boolean mayNeedSplash = false;

  /**
   * Flag to inidcate that we have already transitioned away from the
   * project list to a project (auto-open feature).
   */
  private boolean didTransitionFromProjectList = false;

  /**
   * Flag indicating that we have shown the splash screens.
   */
  private boolean didShowSplash = false;

  private SplashConfig splashConfig; // Splash Screen Configuration

  /**
   * Returns global instance of Ode.
   *
   * @return  global Ode instance
   */
  public static Ode getInstance() {
    return instance;
  }

  /**
   * Returns instance of the aggregate image bundle for the application.
   *
   * @return  image bundle
   */
  public static Images getImageBundle() {
    return IMAGES;
  }

  /**
   * Returns the editor registry.
   *
   * @return the editor registry
   */
  public static ProjectEditorRegistry getProjectEditorRegistry() {
    return EDITORS;
  }

  /**
   * Returns the command registry.
   *
   * @return the command registry
   */
  public static CommandRegistry getCommandRegistry() {
    return COMMANDS;
  }

  /**
   * Returns the system config.
   *
   * @return  system config
   */
  public static Config getSystemConfig() {
    return config;
  }

  /**
   * Returns the user settings.
   *
   * @return  user settings
   */
  public static UserSettings getUserSettings() {
    return userSettings;
  }

  /**
   * Returns the gallery settings.
   *
   * @return  gallery settings
   */
  public static GallerySettings getGallerySettings() {
    return gallerySettings;
  }

  /**
   * loads the gallery settings from server
   *
   */
  public void  loadGallerySettings() {
     // Callback for when the server returns us the apps
    final Ode ode = Ode.getInstance();
    final OdeAsyncCallback<GallerySettings> callback = new OdeAsyncCallback<GallerySettings>(
    // failure message
    MESSAGES.gallerySettingsError()) {
      @Override
      public void onSuccess(GallerySettings settings) {
        gallerySettings = settings;
        if(gallerySettings.galleryEnabled() == true){
          ProjectListBox.getProjectListBox().getProjectList().setPublishedHeaderVisible(true);
          projectToolbar.setPublishOrUpdateButtonVisible(true);
          GalleryClient.getInstance().setSystemEnvironment(settings.getEnvironment());
          topPanel.showGalleryLink(true);
          if(user.isModerator()){
            topPanel.showModerationLink(true);
          }
          topPanel.updateAccountMessageButton();
        }else{
          topPanel.showModerationLink(false);
          topPanel.showGalleryLink(false);
          projectToolbar.setPublishOrUpdateButtonVisible(false);
          ProjectListBox.getProjectListBox().getProjectList().setPublishedHeaderVisible(false);
        }
      }
    };
    //this is below the call back, but of course it is done first
    ode.getGalleryService().loadGallerySettings(callback);
  }

  /**
   * Returns the asset manager.
   *
   * @return  asset manager
   */
  public AssetManager getAssetManager() {
    return assetManager;
  }

  /**
   * Returns true if we have received the window closing event.
   */
  public static boolean isWindowClosing() {
    return getInstance().windowClosing;
  }

  /**
   * Get the current view
   */
  public int getCurrentView() {
    return currentView;
  }

  /**
   * Switch to the Projects tab
   */
  public void switchToProjectsView() {
    // We may need to pass the code below as a runnable to
    // screenShotMaybe() so build the runnable now
    hideTutorials();
    Runnable next = new Runnable() {
        @Override
        public void run() {
          if(currentView != PROJECTS) { //If we are switching to projects view from somewhere else, clear all of the previously selected projects.
            ProjectListBox.getProjectListBox().getProjectList().getSelectedProjects().clear();
            ProjectListBox.getProjectListBox().getProjectList().refreshTable(false);
          }
          currentView = PROJECTS;
          getTopToolbar().updateFileMenuButtons(currentView);
          deckPanel.showWidget(projectsTabIndex);
          // If we started a project, then the start button was disabled (to avoid
          // a second press while the new project wizard was starting (aka we "debounce"
          // the button). When the person switches to the projects list view again (here)
          // we re-enable it.
          projectToolbar.enableStartButton();
        }
      };
    if (designToolbar.getCurrentView() != DesignToolbar.View.BLOCKS) {
      next.run();
    } else {
      // maybe take a screenshot, second argument is true so we wait for i/o to complete
      screenShotMaybe(next, true);
    }
  }

  /**
   * Switch to the User Admin Panel
   */

  public void switchToUserAdminPanel() {
    hideTutorials();
    currentView = USERADMIN;
    deckPanel.showWidget(userAdminTabIndex);
  }

  /**
   * Switch to the Gallery tab
   */
  public void switchToGalleryView() {
    hideTutorials();
    if (!galleryInitialized) {
      // Gallery initialization is deferred until now.
      initializeGallery();
    }
    currentView = GALLERY;
    deckPanel.showWidget(galleryTabIndex);
  }

  /**
   * Switch to the Gallery App
   */
  public void switchToGalleryAppView(GalleryApp app, int editStatus) {
    hideTutorials();
    if (!galleryInitialized) {
      // Gallery initialization is deferred until now.
      initializeGallery();
    }
    currentView = GALLERYAPP;
    GalleryAppBox.setApp(app, editStatus);
    deckPanel.showWidget(galleryAppTabIndex);
  }

  /**
   * Switch to the user profile
   * TODO: change string parameter
   */
  public void switchToUserProfileView(String userId, int editStatus) {
    hideTutorials();
    currentView = USERPROFILE;
    OdeLog.log("###########" + userId + "||||||" + editStatus);
    ProfileBox.setProfile(userId, editStatus);
    deckPanel.showWidget(userProfileTabIndex);
  }

  /**
   * Switch to the Designer tab. Shows an error message if there is no currentFileEditor.
   */
  public void switchToDesignView() {
    // Only show designer if there is a current editor.
    // ***** THE DESIGNER TAB DOES NOT DISPLAY CORRECTLY IF THERE IS NO CURRENT EDITOR. *****
    showTutorials();
    currentView = DESIGNER;
    getTopToolbar().updateFileMenuButtons(currentView);
    if (currentFileEditor != null) {
      deckPanel.showWidget(designTabIndex);
    } else if (!editorManager.hasOpenEditor()) {  // is there a project editor pending visibility?
      OdeLog.wlog("No current file editor to show in designer");
      ErrorReporter.reportInfo(MESSAGES.chooseProject());
    }
  }

  /**
   * Switch to Gallery TabPanel
   */
  public void switchToPrivateUserProfileView() {
    currentView = privateUserProfileIndex;
    deckPanel.showWidget(privateUserProfileIndex);
  }

  /**
   * Switch to the Moderation Page tab
   */
  public void switchToModerationPageView() {
    hideTutorials();
    if (!galleryInitialized) {
      initializeGallery();
    }
    currentView = MODERATIONPAGE;
    deckPanel.showWidget(moderationPageTabIndex);
  }
  /**
   * Switch to the Debugging tab
   */
  public void switchToDebuggingView() {
    hideTutorials();
    deckPanel.showWidget(debuggingTabIndex);

    // NOTE(lizlooney) - Calling resizeWorkArea for debuggingTab prevents the
    // boxes from overlapping each other.
    resizeWorkArea((WorkAreaPanel) deckPanel.getWidget(debuggingTabIndex));
  }

  public void openPreviousProject() {
    if (userSettings == null) {
      OdeLog.wlog("Ignoring openPreviousProject() since userSettings is null");
      return;
    }
    OdeLog.log("Ode.openPreviousProject called");
    final String value = userSettings.getSettings(SettingsConstants.USER_GENERAL_SETTINGS).
      getPropertyValue(SettingsConstants.GENERAL_SETTINGS_CURRENT_PROJECT_ID);

    // Retrieve the userTemplates
    String userTemplates = userSettings.getSettings(SettingsConstants.USER_GENERAL_SETTINGS).
      getPropertyValue(SettingsConstants.USER_TEMPLATE_URLS);
    TemplateUploadWizard.setStoredTemplateUrls(userTemplates);

    if (templateLoadingFlag) {  // We are loading a template, open it instead
                                // of the last project
      NewProjectCommand callbackCommand = new NewProjectCommand() {
          @Override
          public void execute(Project project) {
            templateLoadingFlag = false;
            Ode.getInstance().openYoungAndroidProjectInDesigner(project);
          }
        };
      TemplateUploadWizard.openProjectFromTemplate(templatePath, callbackCommand);
    } else if(galleryIdLoadingFlag){
      try {
        long galleryId_Long = Long.valueOf(galleryId);
        final OdeAsyncCallback<GalleryApp> callback = new OdeAsyncCallback<GalleryApp>(
            // failure message
            MESSAGES.galleryError()) {
              @Override
              public void onSuccess(GalleryApp app) {
                if(app == null){
                  openProject(value);
                  Window.alert(MESSAGES.galleryIdNotExist());
                }else{
                  Ode.getInstance().switchToGalleryAppView(app, GalleryPage.VIEWAPP);
                }
              }
            };
        Ode.getInstance().getGalleryService().getApp(galleryId_Long, callback);
      } catch (NumberFormatException e) {
        openProject(value);
        Window.alert(MESSAGES.galleryIdNotExist());
      }
    } else {
      openProject(value);
    }
  }

  private void openProject(String projectIdString) {
    OdeLog.log("Ode.openProject called for " + projectIdString);
    if (projectIdString.equals("")) {
      openPreviousProject();
    } else if (!projectIdString.equals("0")) {
      final long projectId = Long.parseLong(projectIdString);
      Project project = projectManager.getProject(projectId);
      if (project != null) {
        openYoungAndroidProjectInDesigner(project);
      } else {
        // The project hasn't been added to the ProjectManager yet.
        // Add a ProjectManagerEventListener so we'll be notified when it has been added.
        // Alternatively, it is an invalid projectId. In which case,
        // nothing happens since if the listener eventually fires
        // it will not match the projectId.
        projectManager.addProjectManagerEventListener(new ProjectManagerEventAdapter() {
          @Override
          public void onProjectAdded(Project project) {
            if (project.getProjectId() == projectId) {
              projectManager.removeProjectManagerEventListener(this);
              openYoungAndroidProjectInDesigner(project);
            }
          }
          @Override
          public void onProjectsLoaded() {
            // we only get here iff onProjectAdded is never called with the target project id
            projectManager.removeProjectManagerEventListener(this);
            switchToProjectsView();  // the user will need to select a project...
            ErrorReporter.reportInfo(MESSAGES.chooseProject());
          }
        });
      }
    }
    // else projectIdString == 0; do nothing
  }

  public void openYoungAndroidProjectInDesigner(final Project project) {
    ProjectRootNode projectRootNode = project.getRootNode();
    if (projectRootNode == null) {
      // The project nodes haven't been loaded yet.
      // Add a ProjectChangeListener so we'll be notified when they have been loaded.
      project.addProjectChangeListener(new ProjectChangeAdapter() {
        @Override
        public void onProjectLoaded(Project projectLoaded) {
          project.removeProjectChangeListener(this);
          openYoungAndroidProjectInDesigner(project);
        }
      });
      project.loadProjectNodes();

    } else {
      // The project nodes have been loaded. Tell the viewer to open
      // the project. This will cause the projects source files to be fetched
      // asynchronously, and loaded into file editors.
      ViewerBox.getViewerBox().show(projectRootNode);
      // Note: we can't call switchToDesignView until the Screen1 file editor
      // finishes loading. We leave that to setCurrentFileEditor(), which
      // will get called at the appropriate time.
      String projectIdString = Long.toString(project.getProjectId());
      if (!History.getToken().equals(projectIdString)) {
        // insert token into history but do not trigger listener event
        History.newItem(projectIdString, false);
      }
      if (assetManager == null) {
        assetManager = AssetManager.getInstance();
      }
      assetManager.loadAssets(project.getProjectId());
    }
    getTopToolbar().updateFileMenuButtons(1);
  }

  /**
   * Returns i18n compatible messages
   * @return messages
   */
  public static OdeMessages getMessages() {
    return MESSAGES;
  }

  /**
   * Returns the rpcStatusPopup object.
   * @return RpcStatusPopup
   */
  public static RpcStatusPopup getRpcStatusPopup() {
    return rpcStatusPopup;
  }

  /**
   * Main entry point for Ode. Setting up the UI and the web service
   * connections.
   */
  @Override
  public void onModuleLoad() {
    Tracking.trackPageview();

    // Handler for any otherwise unhandled exceptions
    GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler() {
      @Override
      public void onUncaughtException(Throwable e) {
        OdeLog.xlog(e);

        if (AppInventorFeatures.sendBugReports()) {
          if (Window.confirm(MESSAGES.internalErrorReportBug())) {
            Window.open(BugReport.getBugReportLink(e), "_blank", "");
          }
        } else {
          // Display a confirm dialog with error msg and if 'ok' open the debugging view
          if (Window.confirm(MESSAGES.internalErrorClickOkDebuggingView())) {
            Ode.getInstance().switchToDebuggingView();
          }
        }
      }
    });

    // Initialize global Ode instance
    instance = this;

    // Let's see if we were started with a repo= parameter which points to a template
    templatePath = Window.Location.getParameter("repo");
    if (templatePath != null) {
      OdeLog.wlog("Got a template path of " + templatePath);
      templateLoadingFlag = true;
    }

    // Let's see if we were started with a galleryId= parameter which points to a template
    galleryId = Window.Location.getParameter("galleryId");
    if(galleryId != null){
      OdeLog.wlog("Got a galleryId of " + galleryId);
      galleryIdLoadingFlag = true;
    }

    // Get user information.
    OdeAsyncCallback<Config> callback = new OdeAsyncCallback<Config>(
        // failure message
        MESSAGES.serverUnavailable()) {

      @Override
      public void onSuccess(Config result) {
        config = result;
        user = result.getUser();
        isReadOnly = user.isReadOnly();

        // Setup noop timer (if enabled)
        int noop = config.getNoop();
        if (noop > 0) {
          // If we have a noop time, setup a timer to do the noop
          Timer t = new Timer() {
              @Override
              public void run() {
                userInfoService.noop(new AsyncCallback<Void>() {
                    @Override
                    public void onSuccess(Void e) {
                    }
                    @Override
                    public void onFailure(Throwable e) {
                    }
                  });
              }
            };
            t.scheduleRepeating(1000*60*noop);
        }

        // If user hasn't accepted terms of service, ask them to.
        if (!user.getUserTosAccepted() && !isReadOnly) {
          // We expect that the redirect to the TOS page should be handled
          // by the onFailure method below. The server should return a
          // "forbidden" error if the TOS wasn't accepted.
          ErrorReporter.reportError(MESSAGES.serverUnavailable());
          return;
        }

        splashConfig = result.getSplashConfig();

        if (result.getRendezvousServer() != null) {
          setRendezvousServer(result.getRendezvousServer());
        } else {
          setRendezvousServer(YaVersion.RENDEZVOUS_SERVER);
        }

        userSettings = new UserSettings(user);

        // Gallery settings
        gallerySettings = new GallerySettings();
        //gallerySettings.loadGallerySettings();
        loadGallerySettings();

        // Initialize project and editor managers
        // The project manager loads the user's projects asynchronously
        projectManager = new ProjectManager();
        projectManager.addProjectManagerEventListener(new ProjectManagerEventAdapter() {
          @Override
          public void onProjectsLoaded() {
            projectManager.removeProjectManagerEventListener(this);

            // This handles any built-in templates stored in /war
            // Retrieve template data stored in war/templates folder and
            // and save it for later use in TemplateUploadWizard
            OdeAsyncCallback<String> templateCallback =
                new OdeAsyncCallback<String>(
                  // failure message
                  MESSAGES.createProjectError()) {
                  @Override
                  public void onSuccess(String json) {
                    // Save the templateData
                    TemplateUploadWizard.initializeBuiltInTemplates(json);
                    // Here we call userSettings.loadSettings, but the settings are actually loaded
                    // asynchronously, so this loadSettings call will return before they are loaded.
                    // After the user settings have been loaded, openPreviousProject will be called.
                    // We have to call this after the builtin templates have been loaded otherwise
                    // we will get a NPF.
                    userSettings.loadSettings();
                  }
                };
            Ode.getInstance().getProjectService().retrieveTemplateData(TemplateUploadWizard.TEMPLATES_ROOT_DIRECTORY, templateCallback);
          }
        });
        editorManager = new EditorManager();

        // Initialize UI
        initializeUi();

        topPanel.showUserEmail(user.getUserEmail());
      }

      @Override
      public void onFailure(Throwable caught) {
        if (caught instanceof StatusCodeException) {
          StatusCodeException e = (StatusCodeException) caught;
          int statusCode = e.getStatusCode();
          switch (statusCode) {
            case Response.SC_UNAUTHORIZED:
              // unauthorized => not on whitelist
              // getEncodedResponse() gives us the message that we wrote in
              // OdeAuthFilter.writeWhitelistErrorMessage().
              Window.alert(e.getEncodedResponse());
              return;
            case Response.SC_FORBIDDEN:
              // forbidden => need tos accept
              Window.open("/" + ServerLayout.YA_TOS_FORM, "_self", null);
              return;
            case Response.SC_PRECONDITION_FAILED:
              String locale = Window.Location.getParameter("locale");
              String repo = Window.Location.getParameter("repo");
              galleryId = Window.Location.getParameter("galleryId");
              String separator = "?";
              String uri = "/login/";
              if (locale != null && !locale.equals("")) {
                uri += separator + "locale=" + locale;
                separator = "&";
              }
              if (repo != null && !repo.equals("")) {
                uri += separator + "repo=" + repo;
                separator = "&";
              }
              if (galleryId != null && !galleryId.equals("")) {
                uri += separator + "galleryId=" + galleryId;
              }
              Window.Location.replace(uri);
              return;           // likely not reached
          }
        }
        super.onFailure(caught);
      }
    };

    // The call below begins an asynchronous read of the user's settings
    // When the settings are finished reading, various settings parsers
    // will be called on the returned JSON object. They will call various
    // other functions in this module, including openPreviousProject (the
    // previous project ID is stored in the settings) as well as the splash
    // screen displaying functions below.
    //
    // TODO(user): ODE makes too many RPC requests at startup time. Currently
    // we do 3 RPCs + 1 per project + 1 per open file. We should bundle some of
    // those with each other or with the initial HTML transfer.
    //
    // This call also stores our sessionId in the backend. This will be checked
    // when we go to save a file and if different file saving will be disabled
    // Newer sessions invalidate older sessions.

    userInfoService.getSystemConfig(sessionId, callback);

    // We fetch the user's backpack here. This runs asynchronously with the rest
    // of the system initialization.

    userInfoService.getUserBackpack(new AsyncCallback<String>() {
        @Override
        public void onSuccess(String backpack) {
          BlocklyPanel.setInitialBackpack(backpack);
        }
        @Override
        public void onFailure(Throwable caught) {
          OdeLog.log("Fetching backpack failed");
        }
      });

    History.addValueChangeHandler(new ValueChangeHandler<String>() {
      @Override
      public void onValueChange(ValueChangeEvent<String> event) {
        openProject(event.getValue());
      }
    });

    // load project based on current url
    // TODO(sharon): Seems like a possible race condition here if the onValueChange
    // handler defined above gets called before the getSystemConfig call sets
    // userSettings.
    // The following line causes problems with GWT debugging, and commenting
    // it out doesn't seem to break things.
    //History.fireCurrentHistoryState();
  }

  /*
   * Initializes all UI elements.
   */
  private void initializeUi() {
    rpcStatusPopup = new RpcStatusPopup();

    // Register services with RPC status popup
    rpcStatusPopup.register((ExtendedServiceProxy<?>) helpService);
    rpcStatusPopup.register((ExtendedServiceProxy<?>) projectService);
    rpcStatusPopup.register((ExtendedServiceProxy<?>) galleryService);
    rpcStatusPopup.register((ExtendedServiceProxy<?>) userInfoService);

    Window.setTitle(MESSAGES.titleYoungAndroid());
    Window.enableScrolling(true);

    topPanel = new TopPanel();
    statusPanel = new StatusPanel();

    DockPanel mainPanel = new DockPanel();
    mainPanel.add(topPanel, DockPanel.NORTH);

    // Create the Tutorial Panel
    tutorialPanel = new TutorialPanel();
    tutorialPanel.setWidth("100%");
    tutorialPanel.setHeight("100%");
    // Initially we do not display it. If the project we load has
    // a tutorial URL, then we will set this visible when we load
    // the project
    tutorialPanel.setVisible(false);

    // Create tab panel for subsequent tabs
    deckPanel = new DeckPanel() {
      @Override
      public final void onBrowserEvent(Event event) {
        switch (event.getTypeInt()) {
          case Event.ONCONTEXTMENU:
            event.preventDefault();
            break;
        }
      }
    };

    deckPanel.setAnimationEnabled(true);
    deckPanel.sinkEvents(Event.ONCONTEXTMENU);
    deckPanel.setStyleName("ode-DeckPanel");

    // Projects tab
    VerticalPanel pVertPanel = new VerticalPanel() {
        /**
         * Flag to indicate the project list has been rendered at least once.
         */
        private boolean rendered = false;

        // Override to add splash screen behavior after leaving the project list
        @Override
        public void setVisible(boolean visible) {
          super.setVisible(visible);
          if (visible && !rendered) {
            // setVisible(false) will be called during UI initialization.
            // this flag indicates we are now being shown (possibly again...)
            rendered = true;
            maybeShowSplash2();  // in case of new user; they have no projects!
          } else if (rendered && !visible && (mayNeedSplash || shouldShowWelcomeDialog())
                     && !didShowSplash) {
            showSplashScreens();
          }
        }
      };
    pVertPanel.setWidth("100%");
    pVertPanel.setSpacing(0);
    HorizontalPanel projectListPanel = new HorizontalPanel();
    projectListPanel.setWidth("100%");
    projectToolbar = new ProjectToolbar();
    projectListPanel.add(ProjectListBox.getProjectListBox());
    pVertPanel.add(projectToolbar);
    pVertPanel.add(projectListPanel);
    projectsTabIndex = deckPanel.getWidgetCount();
    deckPanel.add(pVertPanel);

    // Design tab
    VerticalPanel dVertPanel = new VerticalPanel();
    dVertPanel.setWidth("100%");
    dVertPanel.setHeight("100%");

    // Add the Code Navigation arrow
//    switchToBlocksButton = new VerticalPanel();
//    switchToBlocksButton.setVerticalAlignment(VerticalPanel.ALIGN_MIDDLE);
//    switchToBlocksButton.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
//    switchToBlocksButton.setStyleName("ode-NavArrow");
//    switchToBlocksButton.add(new Image(RIGHT_ARROW_IMAGE_URL));
//    switchToBlocksButton.setWidth("25px");
//    switchToBlocksButton.setHeight("100%");

    // Add the Code Navigation arrow
//    switchToDesignerButton = new VerticalPanel();
//    switchToDesignerButton.setVerticalAlignment(VerticalPanel.ALIGN_MIDDLE);
//    switchToDesignerButton.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
//    switchToDesignerButton.setStyleName("ode-NavArrow");
//    switchToDesignerButton.add(new Image(LEFT_ARROW_IMAGE_URL));
//    switchToDesignerButton.setWidth("25px");
//    switchToDesignerButton.setHeight("100%");

    designToolbar = new DesignToolbar();
    dVertPanel.add(designToolbar);

    workColumns = new HorizontalPanel();
    workColumns.setWidth("100%");

    //workColumns.add(switchToDesignerButton);

    Box palletebox = PaletteBox.getPaletteBox();
    palletebox.setWidth("240px");
    workColumns.add(palletebox);

    Box viewerbox = ViewerBox.getViewerBox();
    workColumns.add(viewerbox);
    workColumns.setCellWidth(viewerbox, "97%");
    workColumns.setCellHeight(viewerbox, "97%");

    structureAndAssets = new VerticalPanel();
    structureAndAssets.setVerticalAlignment(VerticalPanel.ALIGN_TOP);
    // Only one of the SourceStructureBox and the BlockSelectorBox is visible
    // at any given time, according to whether we are showing the form editor
    // or the blocks editor. They share the same screen real estate.
    structureAndAssets.add(SourceStructureBox.getSourceStructureBox());
    structureAndAssets.add(BlockSelectorBox.getBlockSelectorBox());  // initially not visible
    structureAndAssets.add(AssetListBox.getAssetListBox());
    workColumns.add(structureAndAssets);

    Box propertiesbox = PropertiesBox.getPropertiesBox();
    propertiesbox.setWidth("222px");
    workColumns.add(propertiesbox);
    //switchToBlocksButton.setHeight("650px");
    //workColumns.add(switchToBlocksButton);
    dVertPanel.add(workColumns);
    designTabIndex = deckPanel.getWidgetCount();
    deckPanel.add(dVertPanel);

    // Gallery list tab
    VerticalPanel gVertPanel = new VerticalPanel();
    gVertPanel.add(createLoadingWidget(GalleryList.INITIAL_RPCS));
    galleryTabIndex = deckPanel.getWidgetCount();
    deckPanel.add(gVertPanel);

     // Gallery app tab
    VerticalPanel aVertPanel = new VerticalPanel();
    galleryAppTabIndex = deckPanel.getWidgetCount();
    deckPanel.add(aVertPanel);

    // User Admin Panel
    VerticalPanel uaVertPanel = new VerticalPanel();
    uaVertPanel.setWidth("100%");
    uaVertPanel.setSpacing(0);
    HorizontalPanel adminUserListPanel = new HorizontalPanel();
    adminUserListPanel.setWidth("100%");
    adminUserListPanel.add(AdminUserListBox.getAdminUserListBox());
    uaVertPanel.add(adminUserListPanel);
    userAdminTabIndex = deckPanel.getWidgetCount();
    deckPanel.add(uaVertPanel);

    // KM: DEBUGGING BEGIN
    // User profile tab
    VerticalPanel uVertPanel = new VerticalPanel();
    uVertPanel.setWidth("100%");
    uVertPanel.setSpacing(0);
    HorizontalPanel userProfilePanel = new HorizontalPanel();
    userProfilePanel.setWidth("100%");
    userProfilePanel.add(ProfileBox.getUserProfileBox());

    uVertPanel.add(userProfilePanel);
    userProfileTabIndex = deckPanel.getWidgetCount();
    deckPanel.add(uVertPanel);
    // KM: DEBUGGING END

    // Private User Profile TabPanel
    VerticalPanel ppVertPanel = new VerticalPanel();
    ppVertPanel.setWidth("100%");
    ppVertPanel.setSpacing(0);
    HorizontalPanel privateUserProfileTabPanel = new HorizontalPanel();
    privateUserProfileTabPanel.setWidth("100%");
    privateUserProfileTabPanel.add(PrivateUserProfileTabPanel.getPrivateUserProfileTabPanel());
    ppVertPanel.add(privateUserProfileTabPanel);
    privateUserProfileIndex = deckPanel.getWidgetCount();
    deckPanel.add(ppVertPanel);

    // Moderation Page tab
    VerticalPanel mPVertPanel = new VerticalPanel();
    mPVertPanel.add(createLoadingWidget(ReportList.INITIAL_RPCS));
    moderationPageTabIndex = deckPanel.getWidgetCount();
    deckPanel.add(mPVertPanel);

    // Debugging tab
    if (AppInventorFeatures.hasDebuggingView()) {

      Button dismissButton = new Button(MESSAGES.dismissButton());
      dismissButton.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
          if (currentView == DESIGNER)
            switchToDesignView();
          else
            switchToProjectsView();
        }
      });

      ColumnLayout defaultLayout = new ColumnLayout("Default");
      Column column = defaultLayout.addColumn(100);
      column.add(MessagesOutputBox.class, 300, false);
      column.add(OdeLogBox.class, 300, false);
      final WorkAreaPanel debuggingTab = new WorkAreaPanel(new OdeBoxRegistry(), defaultLayout);

      debuggingTab.add(dismissButton);

      debuggingTabIndex = deckPanel.getWidgetCount();
      deckPanel.add(debuggingTab);

      // Hook the window resize event, so that we can adjust the UI.
      Window.addResizeHandler(new ResizeHandler() {
        @Override
        public void onResize(ResizeEvent event) {
          resizeWorkArea(debuggingTab);
        }
      });

      // Call the window resized handler to get the initial sizes setup. Doing this in a deferred
      // command causes it to occur after all widgets' sizes have been computed by the browser.
      DeferredCommand.addCommand(new Command() {
        @Override
        public void execute() {
          resizeWorkArea(debuggingTab);
        }
      });

      resizeWorkArea(debuggingTab);
    }

    // We do not select the designer tab here because at this point there is no current project.
    // Instead, we select the projects tab. If the user has a previously opened project, we will
    // open it and switch to the designer after the user settings are loaded.
    // Remember, the user may not have any projects at all yet.
    // Or, the user may have deleted their previously opened project.
    // ***** THE DESIGNER TAB DOES NOT DISPLAY CORRECTLY IF THERE IS NO CURRENT PROJECT. *****
    deckPanel.showWidget(projectsTabIndex);

    overDeckPanel = new HorizontalPanel();
    overDeckPanel.setHeight("100%");
    overDeckPanel.setWidth("100%");
    overDeckPanel.add(tutorialPanel);
    overDeckPanel.setCellWidth(tutorialPanel, "0%");
    overDeckPanel.setCellHeight(tutorialPanel, "100%");
    overDeckPanel.add(deckPanel);
    mainPanel.add(overDeckPanel, DockPanel.CENTER);
    mainPanel.setCellHeight(overDeckPanel, "100%");
    mainPanel.setCellWidth(overDeckPanel, "100%");

//    mainPanel.add(switchToDesignerButton, DockPanel.WEST);
//    mainPanel.add(switchToBlocksButton, DockPanel.EAST);

    //Commenting out for now to gain more space for the blocks editor
    mainPanel.add(statusPanel, DockPanel.SOUTH);
    mainPanel.setSize("100%", "100%");
    RootPanel.get().add(mainPanel);

    // Add a handler to the RootPanel to keep track of Google Chrome Pinch Zooming and
    // handle relevant bugs. Chrome maps a Pinch Zoom to a MouseWheelEvent with the
    // control key pressed.
    RootPanel.get().addDomHandler(new MouseWheelHandler() {
      @Override
      public void onMouseWheel(MouseWheelEvent event) {
        if(event.isControlKeyDown()) {
          // Trip the appropriate flag in PZAwarePositionCallback when the page
          // is Pinch Zoomed. Note that this flag does not need to be removed when
          // the browser is un-zoomed because the patched function for determining
          // absolute position works in all circumstances.
          PZAwarePositionCallback.setPinchZoomed(true);
{'fraction_non_alphanumeric': 0.09830950444075465, 'fraction_numerical': 0.0031982348418155278, 'mean_word_length': 3.018179375811579, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6614021', 'n_tokens_mistral': 24852, 'n_tokens_neox': 23967, 'n_words': 6070}