/**
 * Copyright (c) 2003 IBM Corporation and others. All rights reserved.   This program and the accompanying materials are made
 * available under the terms of the Common Public License v1.0 which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html  * Contributors: IBM - Initial API and implementation
 */

//TODO 1. Handle the sizing of a popup running in shelled out secondary window.
//TODO 2. Support printing: waiting on eclipse bug 47937/44823.
package net.sourceforge.phpeclipse.webbrowser.internal;

import java.util.Iterator;

import net.sourceforge.phpeclipse.webbrowser.IURLMap;

import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.CloseWindowListener;
import org.eclipse.swt.browser.LocationEvent;
import org.eclipse.swt.browser.LocationListener;
import org.eclipse.swt.browser.OpenWindowListener;
import org.eclipse.swt.browser.ProgressEvent;
import org.eclipse.swt.browser.ProgressListener;
import org.eclipse.swt.browser.StatusTextEvent;
import org.eclipse.swt.browser.StatusTextListener;
import org.eclipse.swt.browser.TitleEvent;
import org.eclipse.swt.browser.TitleListener;
import org.eclipse.swt.browser.WindowEvent;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.PlatformUI;

public class WebBrowser extends Composite {
  protected Composite toolbarComp;

  protected Composite statusComp;

  protected Combo combo;

  protected Clipboard clipboard;

  protected boolean showToolbar;

  protected ToolItem back;

  protected ToolItem forward;

  protected ToolItem stop;

  protected ToolItem favorites;

  protected ToolItem refresh;

  protected BusyIndicator busy;

  protected boolean showStatusbar;

  protected ProgressBar progress;

  protected Label status;

  private static int MAX_HISTORY = 50;

  protected static java.util.List history;

  protected Browser browser;

  protected Shell shell;

  protected WebBrowserEditor editor;

  protected String title;

  public WebBrowser(Composite parent, final boolean showToolbar, final boolean showStatusbar) {
    super(parent, SWT.NONE);

    this.showToolbar = showToolbar;
    this.showStatusbar = showStatusbar;

    GridLayout layout = new GridLayout();
    layout.marginHeight = 3;
    layout.marginWidth = 3;
    layout.horizontalSpacing = 3;
    layout.verticalSpacing = 3;
    layout.numColumns = 1;
    setLayout(layout);
    setLayoutData(new GridData(GridData.FILL_BOTH));
    clipboard = new Clipboard(parent.getDisplay());
    PlatformUI.getWorkbench().getHelpSystem().setHelp(this, ContextIds.WEB_BROWSER);

    if (showToolbar) {
      toolbarComp = new Composite(this, SWT.NONE);
      GridLayout outerLayout = new GridLayout();
      outerLayout.numColumns = 2;
      outerLayout.marginWidth = 0;
      outerLayout.marginHeight = 0;
      toolbarComp.setLayout(outerLayout);
      toolbarComp.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING | GridData.FILL_HORIZONTAL));

      // create the top line, with a combo box for history and a "go" button
      Composite top = new Composite(toolbarComp, SWT.NONE);
      GridLayout topLayout = new GridLayout();
      topLayout.numColumns = 2;
      topLayout.marginWidth = 0;
      topLayout.marginHeight = 0;
      top.setLayout(topLayout);
      top.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_CENTER | GridData.FILL_HORIZONTAL));

      combo = new Combo(top, SWT.DROP_DOWN);

      updateHistory();

      combo.addSelectionListener(new SelectionAdapter() {
        public void widgetSelected(SelectionEvent we) {
          try {
            if (combo.getSelectionIndex() != -1)
              setURL(combo.getItem(combo.getSelectionIndex()));
          } catch (Exception e) {
          }
        }
      });
      combo.addListener(SWT.DefaultSelection, new Listener() {
        public void handleEvent(Event e) {
          setURL(combo.getText());
        }
      });
      combo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
      PlatformUI.getWorkbench().getHelpSystem().setHelp(combo, ContextIds.WEB_BROWSER_URL);

      ToolBar toolbar = new ToolBar(top, SWT.FLAT);
      fillToolBar(toolbar);

      new ToolItem(toolbar, SWT.SEPARATOR);

      busy = new BusyIndicator(toolbarComp, SWT.NONE);
      busy.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
    }

    // create a new SWT Web browser widget, checking once again to make sure we can use it in this environment
//    if (WebBrowserUtil.canUseInternalWebBrowser() & WebBrowserUtil.isInternalBrowserOperational())
    if (WebBrowserUtil.isInternalBrowserOperational())
      this.browser = new Browser(this, SWT.NONE);
    else {
      WebBrowserUtil.openError(WebBrowserUIPlugin.getResource("%errorCouldNotLaunchInternalWebBrowser"));
      return;
    }

    if (showToolbar) {
      back.setEnabled(browser.isBackEnabled());
      forward.setEnabled(browser.isForwardEnabled());
    }

    PlatformUI.getWorkbench().getHelpSystem().setHelp(browser, ContextIds.WEB_BROWSER_WEB);
    GridData data = new GridData();
    data.horizontalAlignment = GridData.FILL;
    data.verticalAlignment = GridData.FILL;
    data.horizontalSpan = 3;
    data.grabExcessHorizontalSpace = true;
    data.grabExcessVerticalSpace = true;
    browser.setLayoutData(data);

    if (showStatusbar)
      createStatusArea(this);

    addBrowserListeners();
  }

  /**
   *
   */
  protected void addBrowserListeners() {
    if (showStatusbar) {
      // respond to Browser StatusTextEvents events by updating the status Text label
      browser.addStatusTextListener(new StatusTextListener() {
        public void changed(StatusTextEvent event) {
          status.setText(event.text);
        }
      });
    }

    /**
     * Add listener for new window creation so that we can instead of opening a separate new window in which the session is lost, we
     * can instead open a new window in a new shell within the browser area thereby maintaining the session.
     */
    browser.addOpenWindowListener(new OpenWindowListener() {
      public void open(WindowEvent event) {
        Shell shell2 = new Shell(getDisplay());
        shell2.setLayout(new FillLayout());
        shell2.setText(WebBrowserUIPlugin.getResource("%viewWebBrowserTitle"));
        shell2.setImage(getShell().getImage());
        WebBrowser browser2 = new WebBrowser(shell2, showToolbar, showStatusbar);
        browser2.shell = shell2;
        event.browser = browser2.browser;
        shell2.open();
      }
    });

    browser.addCloseWindowListener(new CloseWindowListener() {
      public void close(WindowEvent event) {
        // if shell is not null, it must be a secondary popup window, else its an editor window
        if (shell != null)
          shell.dispose();
        else
          editor.closeEditor();
      }
    });

    browser.addProgressListener(new ProgressListener() {
      public void changed(ProgressEvent event) {
        if (event.total == 0)
          return;

        boolean done = (event.current == event.total);

        int percentProgress = event.current * 100 / event.total;
        if (showStatusbar) {
          if (done)
            progress.setSelection(0);
          else
            progress.setSelection(percentProgress);
        }

        if (showToolbar) {
          if (!busy.isBusy() && (percentProgress > 0 && percentProgress < 100)) {
            busy.setBusy(true);
          }
          // Once the progress hits 100 percent, done, set busy to false
          else if (busy.isBusy() && done) {
            busy.setBusy(false);
          }
        }
      }

      public void completed(ProgressEvent event) {
        if (showStatusbar)
          progress.setSelection(0);
        if (showToolbar) {
          busy.setBusy(false);
          back.setEnabled(browser.isBackEnabled());
          forward.setEnabled(browser.isForwardEnabled());
        }
      }
    });

    if (showToolbar) {
      browser.addLocationListener(new LocationListener() {
        public void changed(LocationEvent event) {
          if (!event.top)
            return;
          if (!isHome()) {
            combo.setText(event.location);
            addToHistory(event.location);
            updateHistory();
          } else
            combo.setText("");
        }

        public void changing(LocationEvent event) {
        }
      });
    }

    browser.addTitleListener(new TitleListener() {
      public void changed(TitleEvent event) {
        title = event.title;
      }
    });
  }

  /**
   * Return the underlying browser control.
   *
   * @return org.eclipse.swt.browser.Browser
   */
  public Browser getBrowser() {
    return browser;
  }

  /**
   *
   */
  protected void forward() {
    browser.forward();
  }

  /**
   *
   */
  protected void back() {
    browser.back();
  }

  /**
   *
   */
  protected void stop() {
    browser.stop();
  }

  /**
   *
   */
  protected void navigate(String url) {
    Trace.trace(Trace.FINER, "Navigate: " + url);
    if (url != null && url.equals(getURL())) {
      refresh();
      return;
    }
    browser.setUrl(url);
  }

  /**
   * Refresh the currently viewed page.
   */
  public void refresh() {
    browser.refresh();
  }

  protected void setURL(String url, boolean browse) {
    Trace.trace(Trace.FINEST, "setURL: " + url + " " + browse);
    if (url == null) {
      home();
      return;
    }

    if (url.endsWith(WebBrowserPreference.getHomePageURL().substring(9)))
      return;

    // check URL maps
    Iterator iterator = WebBrowserUtil.getURLMaps().iterator();
    String newURL = null;
    while (iterator.hasNext() && newURL == null) {
      try {
        IURLMap map = (IURLMap) iterator.next();
        newURL = map.getMappedURL(url);
      } catch (Exception e) {
      }
    }
    if (newURL != null)
      url = newURL;

    if (browse)
      navigate(url);

    addToHistory(url);
    updateHistory();
  }

  protected void addToHistory(String url) {
    if (history == null)
      history = WebBrowserPreference.getInternalWebBrowserHistory();
    int found = -1;
    int size = history.size();
    for (int i = 0; i < size; i++) {
      String s = (String) history.get(i);
      if (s.equals(url)) {
        found = i;
        break;
      }
    }

    if (found == -1) {
      if (size >= MAX_HISTORY)
        history.remove(size - 1);
      history.add(0, url);
      WebBrowserPreference.setInternalWebBrowserHistory(history);
    } else if (found != 0) {
      history.remove(found);
      history.add(0, url);
      WebBrowserPreference.setInternalWebBrowserHistory(history);
    }
  }

  public void setURL(String url) {
    setURL(url, true);
  }

  /**
   * Creates the Web browser status area.
   */
  private void createStatusArea(Composite parent) {
    Composite composite = new Composite(parent, SWT.NONE);
    GridLayout layout = new GridLayout();
    layout.numColumns = 2;
    layout.marginHeight = 0;
    layout.marginWidth = 0;
    layout.horizontalSpacing = 4;
    layout.verticalSpacing = 0;
    composite.setLayout(layout);
    composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

    // Add a label for displaying status messages as they are received from the control
    status = new Label(composite, SWT.SINGLE | SWT.READ_ONLY);
    GridData gridData = new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL);
    gridData.horizontalIndent = 2;
    status.setLayoutData(gridData);

    // Add a progress bar to display downloading progress information
    progress = new ProgressBar(composite, SWT.BORDER);
    gridData = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.VERTICAL_ALIGN_FILL);
    gridData.widthHint = 100;
    gridData.heightHint = 10;
    progress.setLayoutData(gridData);
  }

  /**
   *
   */
  public void dispose() {
    super.dispose();

    showStatusbar = false;
    showToolbar = false;

    if (busy != null)
      busy.dispose();
    busy = null;

    browser = null;
  }

  /**
   * Populate the toolbar.
   *
   * @param toolbar
   *          org.eclipse.swt.widgets.ToolBar
   */
  private void fillToolBar(final ToolBar toolbar) {
    ToolItem go = new ToolItem(toolbar, SWT.NONE);
    go.setImage(ImageResource.getImage(ImageResource.IMG_ELCL_NAV_GO));
    go.setHotImage(ImageResource.getImage(ImageResource.IMG_CLCL_NAV_GO));
    go.setDisabledImage(ImageResource.getImage(ImageResource.IMG_DLCL_NAV_GO));
    go.setToolTipText(WebBrowserUIPlugin.getResource("%actionWebBrowserGo"));
    go.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        setURL(combo.getText());
      }
    });

    new ToolItem(toolbar, SWT.SEPARATOR);

    favorites = new ToolItem(toolbar, SWT.DROP_DOWN);
    favorites.setImage(ImageResource.getImage(ImageResource.IMG_ELCL_NAV_FAVORITES));
    favorites.setHotImage(ImageResource.getImage(ImageResource.IMG_CLCL_NAV_FAVORITES));
    favorites.setDisabledImage(ImageResource.getImage(ImageResource.IMG_DLCL_NAV_FAVORITES));
    favorites.setToolTipText(WebBrowserUIPlugin.getResource("%actionWebBrowserFavorites"));

    favorites.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        if (event.detail == SWT.ARROW) {
          Rectangle r = favorites.getBounds();
          showFavorites(toolbar, toolbar.toDisplay(r.x, r.y + r.height));
        } else
          addFavorite();
      }
    });

    // create back and forward actions
    back = new ToolItem(toolbar, SWT.NONE);
    back.setImage(ImageResource.getImage(ImageResource.IMG_ELCL_NAV_BACKWARD));
    back.setHotImage(ImageResource.getImage(ImageResource.IMG_CLCL_NAV_BACKWARD));
    back.setDisabledImage(ImageResource.getImage(ImageResource.IMG_DLCL_NAV_BACKWARD));
    back.setToolTipText(WebBrowserUIPlugin.getResource("%actionWebBrowserBack"));
    back.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        back();
      }
    });

    forward = new ToolItem(toolbar, SWT.NONE);
    forward.setImage(ImageResource.getImage(ImageResource.IMG_ELCL_NAV_FORWARD));
    forward.setHotImage(ImageResource.getImage(ImageResource.IMG_CLCL_NAV_FORWARD));
    forward.setDisabledImage(ImageResource.getImage(ImageResource.IMG_DLCL_NAV_FORWARD));
    forward.setToolTipText(WebBrowserUIPlugin.getResource("%actionWebBrowserForward"));
    forward.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        forward();
      }
    });

    // create refresh, stop, and print actions
    stop = new ToolItem(toolbar, SWT.NONE);
    stop.setImage(ImageResource.getImage(ImageResource.IMG_ELCL_NAV_STOP));
    stop.setHotImage(ImageResource.getImage(ImageResource.IMG_CLCL_NAV_STOP));
    stop.setDisabledImage(ImageResource.getImage(ImageResource.IMG_DLCL_NAV_STOP));
    stop.setToolTipText(WebBrowserUIPlugin.getResource("%actionWebBrowserStop"));
    stop.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        stop();
      }
    });

    refresh = new ToolItem(toolbar, SWT.NONE);
    refresh.setImage(ImageResource.getImage(ImageResource.IMG_ELCL_NAV_REFRESH));
    refresh.setHotImage(ImageResource.getImage(ImageResource.IMG_CLCL_NAV_REFRESH));
    refresh.setDisabledImage(ImageResource.getImage(ImageResource.IMG_DLCL_NAV_REFRESH));
    refresh.setToolTipText(WebBrowserUIPlugin.getResource("%actionWebBrowserRefresh"));
    refresh.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        refresh();
      }
    });
  }

  protected void addFavorite() {
    java.util.List list = WebBrowserPreference.getInternalWebBrowserFavorites();
    Favorite f = new Favorite(title, browser.getUrl());
    if (!list.contains(f)) {
      list.add(f);
      WebBrowserPreference.setInternalWebBrowserFavorites(list);
    }
  }

  protected void showFavorites(Control parent, Point p) {
    Menu perspectiveBarMenu = null;
    if (perspectiveBarMenu == null) {
      Menu menu = new Menu(parent);

      // locked favorites
      Iterator iterator = WebBrowserUtil.getLockedFavorites().iterator();
      if (iterator.hasNext()) {
        while (iterator.hasNext()) {
          final Favorite f = (Favorite) iterator.next();
          MenuItem item = new MenuItem(menu, SWT.NONE);
          item.setText(f.getName());
          item.setImage(ImageResource.getImage(ImageResource.IMG_FAVORITE));
          item.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent event) {
              setURL(f.getURL());
            }
          });
        }

        new MenuItem(menu, SWT.SEPARATOR);
      }

      iterator = WebBrowserPreference.getInternalWebBrowserFavorites().iterator();
      if (!iterator.hasNext()) {
        MenuItem item = new MenuItem(menu, SWT.NONE);
        item.setText(WebBrowserUIPlugin.getResource("%actionWebBrowserNoFavorites"));
      }
      while (iterator.hasNext()) {
        final Favorite f = (Favorite) iterator.next();
        MenuItem item = new MenuItem(menu, SWT.NONE);
        item.setText(f.getName());
        item.setImage(ImageResource.getImage(ImageResource.IMG_FAVORITE));
        item.addSelectionListener(new SelectionAdapter() {
          public void widgetSelected(SelectionEvent event) {
            setURL(f.getURL());
          }
        });
      }

      new MenuItem(menu, SWT.SEPARATOR);

      MenuItem item = new MenuItem(menu, SWT.NONE);
      item.setText(WebBrowserUIPlugin.getResource("%actionWebBrowserOrganizeFavorites"));
      item.addSelectionListener(new SelectionAdapter() {
        public void widgetSelected(SelectionEvent event) {
          OrganizeFavoritesDialog dialog = new OrganizeFavoritesDialog(shell);
          dialog.open();
        }
      });

      perspectiveBarMenu = menu;
    }

    if (perspectiveBarMenu != null) {
      perspectiveBarMenu.setLocation(p.x, p.y);
      perspectiveBarMenu.setVisible(true);
    }
  }

  public void home() {
    navigate(WebBrowserPreference.getHomePageURL());
  }

  /**
   * Returns true if the homepage is currently being displayed.
   *
   * @return boolean
   */
  protected boolean isHome() {
    return getURL() != null && getURL().endsWith(WebBrowserPreference.getHomePageURL().substring(9));
  }

  protected String getURL() {
    return browser.getUrl();
  }

  /**
   * Update the history list to the global copy.
   */
  protected void updateHistory() {
    if (combo == null)
      return;

    String temp = combo.getText();
    if (history == null)
      history = WebBrowserPreference.getInternalWebBrowserHistory();

    String[] historyList = new String[history.size()];
    history.toArray(historyList);
    combo.setItems(historyList);

    combo.setText(temp);
  }

  public void addProgressListener(ProgressListener listener) {
    browser.addProgressListener(listener);
  }

  public void addStatusTextListener(StatusTextListener listener) {
    browser.addStatusTextListener(listener);
  }

  public void addTitleListener(TitleListener listener) {
    browser.addTitleListener(listener);
  }
}