//
// browser.cs: Mono documentation browser
//
// Author:
//   Miguel de Icaza
//
// (C) 2003 Ximian, Inc.
//
// TODO:
//   Add support for printing.
//   Add search facility
//
//Force GECKO on Win32
using Gtk;
using Glade;
using Gecko;
using System;
using System.IO;
using System.Reflection;
using System.Collections;
using System.Web.Services.Protocols;
using System.Xml;
using Microsoft.Win32;
using System.Runtime.InteropServices;

namespace Monodoc ^M
{
class Driver {

        public static bool IsWindowsOS()
        {
                bool retVal = false;
                string comSpec;
                comSpec = System.Environment.GetEnvironmentVariable("COMSPEC");
                if(comSpec != null && comSpec.Length != 0)
                {
                        retVal = true;
                        Console.WriteLine("IsWindowsOS? Yes it is :-)");
                }

                return retVal;
        }

        static int Main (string [] args)
        {
                string grePath = "";

                // Get the GRE for Gecko# Path in Windows systems
                grePath = System.Environment.GetEnvironmentVariable("GECKOSHILLA_BASEPATH");
                if(grePath != null && grePath.Length != 0)
                {
                        Gecko.WebControl.CompPath = grePath;
                }

                WebControl.CompPath = grePath;

                string topic = null;

                for (int i = 0; i < args.Length; i++){
                        switch (args [i]){
                        case "--html":
                                if (i+1 == args.Length){
                                        Console.WriteLine ("--html needed argument");
                                        return 1;
                                }

                                Node n;
                                RootTree help_tree = RootTree.LoadTree ();
                                string res = help_tree.RenderUrl (args [i+1], out n);
                                if (res != null){
                                        Console.WriteLine (res);
                                        return 0;
                                } else {
                                        return 1;
                                }
                        case "--make-index":
                                RootTree.MakeIndex ();
                                return 0;

                        case "--help":
                                Console.WriteLine ("Options are:\n"+
                                                   "browser [--html TOPIC] [--make-index] [TOPIC] [--merge-changes CHANGE_FILE TARGET_DIR+]");
                                return 0;

                        case "--merge-changes":
                                if (i+2 == args.Length) {
                                        Console.WriteLine ("--merge-changes 2+ args");
                                        return 1;
                                }

                                ArrayList targetDirs = new ArrayList ();

                                for (int j = i+2; j < args.Length; j++)
                                        targetDirs.Add (args [j]);

                                EditMerger e = new EditMerger (
                                        GlobalChangeset.LoadFromFile (args [i+1]),
                                        targetDirs
                                );

                                e.Merge ();

                                return 0;

                        case "--local-edit":
                                if (i+1 == args.Length) {
                                        Console.WriteLine ("--local-edit requires an argument");
                                        return 1;
                                }
                                EcmaUncompiledHelpSource hs = new EcmaUncompiledHelpSource(args[i+1]);
                                RootTree.ExtraHelpSources.Add(hs.Name + " (Local Edit)", hs);
                                i++;
                                break;

                        default:
                                topic = args [i];
                                break;
                        }

                }

                SettingsHandler.CheckUpgrade ();

                Settings.RunningGUI = true;

                Browser browser = new Browser ();

                if (topic != null)
                        browser.LoadUrl (topic);

                return 0;
        }
}

class Browser {
        Glade.XML ui;
        Gtk.Window MainWindow;
        Style bar_style;

        [Glade.Widget] public Gtk.Window window1;
        [Glade.Widget] Gtk.TreeView reference_tree;
        [Glade.Widget] Gtk.TreeView bookmark_tree;
        [Glade.Widget] Gtk.ScrolledWindow html_container;
        [Glade.Widget] Gtk.Statusbar statusbar;
        [Glade.Widget] Gtk.Button back_button, forward_button;
        [Glade.Widget] Gtk.Entry index_entry;
        [Glade.Widget] Gtk.CheckMenuItem editing1;
        [Glade.Widget] Gtk.CheckMenuItem showinheritedmembers;
        [Glade.Widget] Gtk.CheckMenuItem comments1;
        [Glade.Widget] Gtk.MenuItem postcomment;
        [Glade.Widget] Gtk.MenuItem paste1;

        [Glade.Widget] Gtk.MenuItem bookmarksMenu;

        //
        // Editor
        //
        [Glade.Widget] Gtk.Notebook html_and_editor_notebook;
        [Glade.Widget] Gtk.TextView text_editor;
        [Glade.Widget] Gtk.ScrolledWindow html_preview_container;

        [Glade.Widget] Gtk.EventBox bar_eb, index_eb;
        [Glade.Widget] Gtk.Label subtitle_label;
        [Glade.Widget] Gtk.Notebook nb;

        [Glade.Widget] Gtk.Box title_label_box;
        ELabel title_label;

        //
        // Accessed from the IndexBrowser class
        //
        [Glade.Widget] internal Gtk.Box search_box;
        [Glade.Widget] internal Gtk.Frame matches;

        Gdk.Pixbuf monodoc_pixbuf;

        public History history;

        // Where we render the contents
        WebControl html;

        // Our HTML preview during editing.
        WebControl html_preview;

        //
        // Left-hand side Browsers
        //
        TreeBrowser tree_browser;
        IndexBrowser index_browser;
        string CurrentUrl;

        internal RootTree help_tree;

        // For the status bar.
        uint context_id;

        // Control of Bookmark
        struct BookLink
        {
                public string Text, Url;

                public BookLink (string text, string url)
                {
                        this.Text = text;
                        this.Url = url;
                }
        }

        public ArrayList bookList;

        public Browser ()
        {
                Application.Init ();
                ui = new Glade.XML (null, "browser.glade", "window1", null);
                ui.Autoconnect (this);

                MainWindow = (Gtk.Window) ui["window1"];
                // MainWindow = window1;
                MainWindow.DeleteEvent += new DeleteEventHandler (delete_event_cb);

                MainWindow.KeyPressEvent += new KeyPressEventHandler (keypress_event_cb);

                Stream icon = GetResourceImage ("monodoc.png");

                if (icon != null){
                        monodoc_pixbuf = new Gdk.Pixbuf (icon);
                        MainWindow.Icon = monodoc_pixbuf;
                }

                //ellipsizing label for the title
                title_label = new ELabel ("");
                title_label.Xalign = 0;
                Pango.FontDescription fd = new Pango.FontDescription ();
                fd.Weight = Pango.Weight.Bold;
                title_label.ModifyFont (fd);
                title_label.Layout.FontDescription = fd;
                title_label_box.Add (title_label);
                title_label.Show ();

                //colour the bar according to the current style
                bar_style = bar_eb.Style.Copy ();
                bar_eb.Style = bar_style;
                MainWindow.StyleSet += new StyleSetHandler (BarStyleSet);
                BarStyleSet (null, null);

                help_tree = RootTree.LoadTree ();
                tree_browser = new TreeBrowser (help_tree, reference_tree, this);

                // restore the editing setting
                editing1.Active = SettingsHandler.Settings.EnableEditing;

                comments1.Active = SettingsHandler.Settings.ShowComments;

                //
                // Setup the HTML rendering area
                //
                if(Driver.IsWindowsOS() == false)
                {
                        html = new WebControl("/tmp/monodoc", "MonoDoc");
                }
                else
                {
                        // TODO: FIX HACK
                        // HACK: Hack for profiling issues. Might later 
                        // prepend something in the gecko# code later
                        html = new WebControl(
                                String.Format("{0}{1}",
                                System.IO.Path.GetTempPath(),
                                System.IO.Path.DirectorySeparatorChar),
                "MonoDoc");
                }

                html.Show ();
                html_container.AddWithViewport(html);
                html.OpenUri += new OpenUriHandler (LinkClicked);
                html.LinkMsg += new EventHandler (OnUrlMouseOver);

                context_id = statusbar.GetContextId ("");

                //
                // Text editor (for editing the documentation).
                //
                html_and_editor_notebook.ShowTabs = false;
                if(Driver.IsWindowsOS() == false)
                {
                        html_preview = new WebControl("/tmp/monodoc_preview", "MonoDoc");
                }
                else
                {
                        // TODO: FIX HACK
                        // HACK: Hack for profiling issues. Might later 
                        // prepend something in the gecko# code later
                        html_preview = new WebControl("/tmp/monodoc_preview" +
                                System.Guid.NewGuid().ToString("N"),
                                "MonoDoc");
                }

                html_preview.Show ();
                html_preview_container.AddWithViewport (html_preview);

                text_editor.Buffer.Changed += new EventHandler (EditedTextChanged);
                text_editor.WrapMode = WrapMode.Word;

                paste1.Sensitive = false;

                //
                // Other bits
                //
                history = new History (back_button, forward_button);
                bookList = new ArrayList ();

                index_browser = IndexBrowser.MakeIndexBrowser (this);

                Node match;
                string s = help_tree.RenderUrl ("root:", out match);
                if (s != null){
                        Render (s, match, "root:");
                        history.AppendHistory (new Browser.LinkPageVisit (this, "root:"));
                }
                Application.Run ();
        }

        public enum Mode {
                Viewer, Editor
        }

        public Mode BrowserMode {
                get {
                        return browser_mode;
                }
                set {
                        browser_mode = value;
                }
        }

        Mode browser_mode;
        public void SetMode (Mode m)
        {
                if (browser_mode == m)
                        return;

                if (m == Mode.Viewer) {
                        html_and_editor_notebook.Page = 0;
                        paste1.Sensitive = false;
                } else {
                        html_and_editor_notebook.Page = 1;
                        paste1.Sensitive = true;
                }

                browser_mode = m;
        }

        void BarStyleSet (object obj, StyleSetArgs args)
        {
                bar_style.SetBackgroundGC (StateType.Normal, MainWindow.Style.BackgroundGCs[1]);
        }

        Stream GetResourceImage (string name)
        {
                Assembly assembly = System.Reflection.Assembly.GetCallingAssembly ();
                System.IO.Stream s = assembly.GetManifestResourceStream (name);

                return s;
        }

        public class LinkPageVisit : PageVisit {
                Browser browser;
                string url;

                public LinkPageVisit (Browser browser, string url)
                {
                        this.browser = browser;
                        this.url = url;
                }

                public override void Go ()
                {
                        Node n;

                        string res = browser.help_tree.RenderUrl (url, out n);
                        browser.Render (res, n, url);
                }
        }

        void LinkClicked (object o, OpenUriArgs args)
        {
                LoadUrl (args.AURI);
                // Mozilla needs a TRUE return value here.
                // This stops Gecko from processing the URI
                // Note, this needs fixing.  Should return a bool
                // type, not an int.  Argh!
                //SignalArgs sa = (SignalArgs) args;
                //sa.RetVal = 1;
        }

        private System.Xml.XmlNode edit_node;
        private string edit_url;

        public void LoadUrl (string url)
        {
                if (url.StartsWith("#"))
                {
                        // FIXME: This doesn't deal with whether anchor jumps should go in the history
                        return;
                }

                if (url.StartsWith ("edit:"))
                {
                        Console.WriteLine ("Node is: " + url);
                        edit_node = EditingUtils.GetNodeFromUrl (url, help_tree);
                        edit_url = url;
                        SetMode (Mode.Editor);
                        text_editor.Buffer.Text = edit_node.InnerXml;
                        return;
                }

                Node node;

                Console.Error.WriteLine ("Trying: {0}", url);
                string res = help_tree.RenderUrl (url, out node);
                if (res != null){
                        Render (res, node, url);
                        history.AppendHistory (new LinkPageVisit (this, url));
                        return;
                }

                Console.Error.WriteLine ("+----------------------------------------------+");
                Console.Error.WriteLine ("| Here we should locate the provider for the   |");
                Console.Error.WriteLine ("| link.  Maybe using this document as a base?  |");
                Console.Error.WriteLine ("| Maybe having a locator interface?   The short|");
                Console.Error.WriteLine ("| urls are not very useful to locate types     |");
                Console.Error.WriteLine ("+----------------------------------------------+");
                Render (url, null, url);
        }

        //
        // Renders the HTML text in `text' which was computed from `url'.
        // The Node matching is `matched_node'.
        //
        // `url' is only used for debugging purposes
        //
        public void Render (string text, Node matched_node, string url)
        {
                CurrentUrl = url;

                string hdr = "<html><title></title><body>";
                string ftr = "</body></html>";

                html.OpenStream("file://", "text/html");
                html.AppendData(hdr);//, (uint)hdr.Length);
                html.AppendData(text);//, (uint)text.Length);
                html.AppendData(ftr);//, (uint)ftr.Length);
                html.CloseStream();

                if (matched_node != null) {
                        if (tree_browser.SelectedNode != matched_node)
                                tree_browser.ShowNode (matched_node);

                        title_label.Text = matched_node.Caption;

                        if (matched_node.Nodes != null) {
                                int count = matched_node.Nodes.Count;
                                string term;

                                if (count == 1)
                                        term = "subpage";
                                else
                                        term = "subpages";

                                subtitle_label.Text = count + " " + term;
                        } else
                                subtitle_label.Text = "";
                } else {
                        title_label.Text = "Error";
                        subtitle_label.Text = "";
                }
        }

        //
        // Invoked when the mouse is over a link
        //
        string last_url = "";
        void OnUrlMouseOver (object o, EventArgs args)
        {
                string new_url = html.LinkMessage;
                if (new_url == null)
                        new_url = "";

                if (new_url != last_url){
                        statusbar.Pop (context_id);
                        statusbar.Push (context_id, new_url);
                        last_url = new_url;
                }
        }

        void keypress_event_cb (object o, KeyPressEventArgs args)
        {
                switch (args.Event.Key) {
                case Gdk.Key.Left:
                        if (((Gdk.ModifierType) args.Event.State &
                        Gdk.ModifierType.Mod1Mask) !=0)
                        history.BackClicked (this, EventArgs.Empty);
                        args.RetVal = true;
                        break;
                case Gdk.Key.Right:
                        if (((Gdk.ModifierType) args.Event.State &
                        Gdk.ModifierType.Mod1Mask) !=0)
                        history.ForwardClicked (this, EventArgs.Empty);
                        args.RetVal = true;
                        break;
                }
        }

        void delete_event_cb (object o, DeleteEventArgs args)
        {
                Application.Quit ();
        }

        void OnCommentsActivate (object o, EventArgs args)
        {
                SettingsHandler.Settings.ShowComments = comments1.Active;

                // postcomment.Sensitive = comments1.Active;

                // refresh, so we can see the comments
                if (history != null) // catch the case when we are currently loading
                        history.ActivateCurrent ();
        }

        void OnInheritedMembersActivate (object o, EventArgs args)
        {
                SettingsHandler.Settings.ShowInheritedMembers = showinheritedmembers.Active;
                if (history != null) // catch the case when we are currently loading
                        history.ActivateCurrent ();
        }

        void OnEditingActivate (object o, EventArgs args)
        {
                SettingsHandler.Settings.EnableEditing = editing1.Active;

                // refresh, so we can see the [edit] things
                if (history != null) // catch the case when we are currently loading
                        history.ActivateCurrent ();
        }

        void OnCollapseActivate (object o, EventArgs args)
        {
                reference_tree.CollapseAll ();
                reference_tree.ExpandRow (new TreePath ("0"), false);
        }

        //
        // Invoked when the index_entry Entry line content changes
        //
        void OnIndexEntryChanged (object sender, EventArgs a)
        {
                if (index_browser != null)
                        index_browser.SearchClosest (index_entry.Text);
        }

        //
        // Invoked when the user presses enter on the index_entry
        //
        void OnIndexEntryActivated (object sender, EventArgs a)
        {
                if (index_browser != null)
                        index_browser.LoadSelected ();
        }

        //
        // Invoked when the user presses a key on the index_entry
        //

        void OnIndexEntryKeyPress (object o, KeyPressEventArgs args)
        {
                args.RetVal = true;

                switch (args.Event.Key) {
                        case Gdk.Key.Up:

                                if (matches.Visible == true && index_browser.match_list.Selected != 0)
                                {
                                        index_browser.match_list.Selected--;
                                } else {
                                        index_browser.index_list.Selected--;
                                        if (matches.Visible == true)
                                                index_browser.match_list.Selected = index_browser.match_model.Rows - 1;
                                }
                                break;

                        case Gdk.Key.Down:

                                if (matches.Visible == true && index_browser.match_list.Selected + 1 != index_browser.match_model.Rows) {
                                        index_browser.match_list.Selected++;
                                } else {
                                        index_browser.index_list.Selected++;
                                        if (matches.Visible == true)
                                                index_browser.match_list.Selected = 0;
                                }
                                break;

                        default:
                                args.RetVal = false;
                                break;
                }
        }

        //
        // For the accel keystroke
        //
        void OnIndexEntryFocused (object sender, EventArgs a)
        {
                nb.Page = 1;
        }

        //
        // Invoked from File/Quit menu entry.
        //
        void OnQuitActivate (object sender, EventArgs a)
        {
                Application.Quit ();
        }

        //
        // Invoked by Edit/Copy menu entry.
        //
        void OnCopyActivate (object sender, EventArgs a)
        {
                        Clipboard cb = Clipboard.Get (Gdk.Selection.Clipboard);
                        text_editor.Buffer.CopyClipboard (cb);
        }

        //
        // Invoked by Edit/Paste menu entry.
        //
        void OnPasteActivate (object sender, EventArgs a)
        {
                Clipboard cb = Clipboard.Get (Gdk.Selection.Clipboard);

                if (!cb.WaitIsTextAvailable ())
                        return;

                string text = cb.WaitForText ();

                text_editor.Buffer.InsertAtCursor (text);
        }

        class About {
                [Glade.Widget] Window about;
                [Glade.Widget] Image logo_image;

                static About AboutBox;
                Browser parent;

                About (Browser parent)
                {
                        Glade.XML ui = new Glade.XML (null, "browser.glade", "about", null);
                        ui.Autoconnect (this);
                        this.parent = parent;

                        about.TransientFor = parent.window1;

                        logo_image.Pixbuf = new Gdk.Pixbuf (null, "monodoc.png");
                }

                void OnOkClicked (object sender, EventArgs a)
                {
                        about.Hide ();
                }

                //
                // Called on the Window delete icon clicked
                //
                void OnDelete (object sender, EventArgs a)
                {
                        AboutBox = null;
                }

                static public void Show (Browser parent)
                {
                        if (AboutBox == null)
                                AboutBox = new About (parent);
                        AboutBox.about.Show ();
                }
        }

        //
        // Hooked up from Glade
        //
        void OnAboutActivate (object sender, EventArgs a)
        {
                About.Show (this);
        }

        void OnUpload (object sender, EventArgs a)
        {
                string key = SettingsHandler.Settings.Key;
                if (key == null || key == "")
                        ConfigWizard.Run (this);
                else
                        DoUpload ();
        }

        void DoUpload ()
        {
                Upload.Run (this);
        }

        class Upload {
                enum State {
                        GetSerial,
                        PrepareUpload,
                        SerialError,
                        VersionError,
                        SubmitError,
                        NetworkError,
                        Done
                }

                [Glade.Widget] Dialog upload_dialog;
                [Glade.Widget] Label status;
                [Glade.Widget] Button cancel;
                State state;
                ThreadNotify tn;
                WebClientAsyncResult war;
                ContributionsSoap d;
                int serial;

                public static void Run (Browser browser)
                {
                        new Upload (browser);
                }

                Upload (Browser browser)
                {
                        tn = new ThreadNotify (new ReadyEvent (Update));
                        Glade.XML ui = new Glade.XML (null, "browser.glade", "upload_dialog", null);
                        ui.Autoconnect (this);
                        d = new ContributionsSoap ();
                        if (Environment.GetEnvironmentVariable ("MONODOCTESTING") == null)
                                d.Url = "http://www.go-mono.com/docs/server.asmx";

                        status.Text = "Checking Server version";
                        war = (WebClientAsyncResult) d.BeginCheckVersion (1, new AsyncCallback (VersionChecked), null);
                }

                void Update ()
                {
                        Console.WriteLine ("In Update: " + state);
                        switch (state){
                        case State.NetworkError:
                                status.Text = "A network error ocurred";
                                cancel.Label = "Close";
                                return;
                        case State.VersionError:
                                status.Text = "Server has a different version, upgrade your MonoDoc";
                                cancel.Label = "Close";
                                return;
                        case State.GetSerial:
                                war = (WebClientAsyncResult) d.BeginGetSerial (
                                        SettingsHandler.Settings.Email, SettingsHandler.Settings.Key,
                                        new AsyncCallback (GetSerialDone), null);
                                return;
                        case State.SerialError:
                                status.Text = "Error obtaining serial number from server for this account";
                                cancel.Label = "Close";
                                return;
                        case State.SubmitError:
                                status.Text = "There was a problem with the documentation uploaded";
                                cancel.Label = "Close";
                                return;

                        case State.PrepareUpload:
                                GlobalChangeset cs = EditingUtils.GetChangesFrom (serial);
                                if (cs == null){
                                        status.Text = "No new contributions";
                                        cancel.Label = "Close";
                                        return;
                                }

                                CopyXmlNodeWriter w = new CopyXmlNodeWriter ();
                                GlobalChangeset.serializer.Serialize (w, cs);
                                Console.WriteLine ("Uploading...");
                                status.Text = String.Format ("Uploading {0} contributions", cs.Count);
                                XmlDocument dd = (XmlDocument) w.Document;
                                war = (WebClientAsyncResult) d.BeginSubmit (
                                        SettingsHandler.Settings.Email, SettingsHandler.Settings.Key,
                                        ((XmlDocument) w.Document).DocumentElement,
                                        new AsyncCallback (UploadDone), null);
                                return;
                        case State.Done:
                                status.Text = "All contributions uploaded";
                                cancel.Label = "Close";
                                SettingsHandler.Settings.SerialNumber = serial;
                                SettingsHandler.Save ();
                                return;
                        }
                }

                void UploadDone (IAsyncResult iar)
                {
                        try {
                                int result = d.EndSubmit (iar);
                                war = null;
                                if (result < 0)
                                        state = State.SubmitError;
                                else {
                                        state = State.Done;
                                        serial = result;
                                }
                        } catch (Exception e) {
                                state = State.NetworkError;
                                Console.WriteLine ("Upload: " + e);
                        }
                        if (tn != null)
                                tn.WakeupMain ();
                }

                void GetSerialDone (IAsyncResult iar)
                {
                        try {
                                serial = d.EndGetSerial (iar);
                                war = null;
                                if (serial < 0)
                                        state = State.SerialError;
                                else
                                        state = State.PrepareUpload;
                        } catch (Exception e) {
                                Console.WriteLine ("Serial: " + e);
                                state = State.NetworkError;
                        }
                        if (tn != null)
                                tn.WakeupMain ();
                }

                void VersionChecked (IAsyncResult iar)
                {
                        try {
                                int ver = d.EndCheckVersion (iar);
                                war = null;
                                if (ver != 0)
                                        state = State.VersionError;
                                else
                                        state = State.GetSerial;
                        } catch (Exception e) {
                                Console.WriteLine ("Version: " + e);
                                state = State.NetworkError;
                        }
                        if (tn != null)
                                tn.WakeupMain ();
                }

                void Cancel_Clicked (object sender, EventArgs a)
                {
                        if (war != null)
                                war.Abort ();
                        war = null;
                        state = State.Done;

                        upload_dialog.Destroy ();
                        upload_dialog = null;
                        tn = null;
                }
        }

        class ConfigWizard {
                static ConfigWizard config_wizard;

                [Glade.Widget] Window window_config_wizard;
                [Glade.Widget] Notebook notebook;
                [Glade.Widget] Button button_email_ok;
                [Glade.Widget] Entry entry_email;
                [Glade.Widget] Entry entry_password;

                Browser parent;
                ContributionsSoap d;
                WebClientAsyncResult war;
                ThreadNotify tn;
                int new_page;

                public static void Run (Browser browser)
                {
                        if (config_wizard == null)
                                config_wizard = new ConfigWizard (browser);
                        return;
                }

                ConfigWizard (Browser browser)
                {
                        tn = new ThreadNotify (new ReadyEvent (UpdateNotebookPage));
                        Glade.XML ui = new Glade.XML (null, "browser.glade", "window_config_wizard", null);
                        ui.Autoconnect (this);
                        //notebook.ShowTabs = false;
                        parent = browser;
                        window_config_wizard.TransientFor = browser.window1;

                        d = new ContributionsSoap ();
                        if (Environment.GetEnvironmentVariable ("MONODOCTESTING") == null)
                                d.Url = "http://www.go-mono.com/docs/server.asmx";
                        notebook.Page = 8;

                        war = (WebClientAsyncResult) d.BeginCheckVersion (1, new AsyncCallback (VersionChecked), null);
                }

                void NetworkError ()
                {
                        new_page = 9;
                        tn.WakeupMain ();
                }

                void VersionChecked (IAsyncResult iar)
                {
                        int ver = -1;

                        try {
                                if (notebook.Page != 8)
                                        return;

                                ver = d.EndCheckVersion (iar);
                                if (ver != 0)
                                        new_page = 10;
                                else
                                        new_page = 0;
                                tn.WakeupMain ();
                        } catch (Exception e){
                                Console.WriteLine ("Error" + e);
                                NetworkError ();
                        }
                }

                //
                // Called on the Window delete icon clicked
                //
                void OnDelete (object sender, EventArgs a)
                {
                        config_wizard = null;
                }

                //
                // called when the license is approved
                //
                void OnPage1_Clicked (object sender, EventArgs a)
                {
                        button_email_ok.Sensitive = false;
                        notebook.Page = 1;
                }

                //
                // Request the user registration.
                //
                void OnPage2_Clicked (object sender, EventArgs a)
                {
                        notebook.Page = 2;
                        SettingsHandler.Settings.Email = entry_email.Text;
                        war = (WebClientAsyncResult) d.BeginRegister (entry_email.Text, new AsyncCallback (RegisterDone), null);
                }

                void UpdateNotebookPage ()
                {
                        notebook.Page = new_page;
                }

                void RegisterDone (IAsyncResult iar)
                {
                        int code;

                        try {
                                Console.WriteLine ("Registration done");
                                code = d.EndRegister (iar);
                                if (code != 0 && code != -2){
                                        NetworkError ();
                                        return;
                                }
                                new_page = 4;
                        } catch {
                                new_page = 3;
                        }
                        tn.WakeupMain ();
                }

                void PasswordContinue_Clicked (object sender, EventArgs a)
                {
                        notebook.Page = 5;
                        SettingsHandler.Settings.Key = entry_password.Text;
                        war = (WebClientAsyncResult) d.BeginGetSerial (entry_email.Text, entry_password.Text, new AsyncCallback (GetSerialDone), null);
                }

                void GetSerialDone (IAsyncResult iar)
                {
                        try {
                                int last = d.EndGetSerial (iar);
                                if (last == -1){
                                        SettingsHandler.Settings.Key = "";
                                        new_page = 11;
                                        tn.WakeupMain ();
                                        return;
                                }

                                SettingsHandler.Settings.SerialNumber = last;
                                new_page = 6;
                                tn.WakeupMain ();
                        } catch {
                                NetworkError ();
                        }
                }

                void AccountRequestCancel_Clicked (object sender, EventArgs a)
                {
                        war.Abort ();
                        notebook.Page = 7;
                }

                void SerialRequestCancel_Clicked (object sender, EventArgs a)
                {
                        war.Abort ();
                        notebook.Page = 7;
                }

                void LoginRequestCancel_Clicked (object sender, EventArgs a)
                {
                        war.Abort ();
                        notebook.Page = 7;
                }

                //
                // Called when the user clicks `ok' on a terminate page
                //
                void Terminate_Clicked (object sender, EventArgs a)
                {
                        window_config_wizard.Destroy ();
                        config_wizard = null;
                }

                // Called when the registration process has been successful
                void Completed_Clicked (object sender, EventArgs a)
                {
                        window_config_wizard.Destroy ();
                        config_wizard = null;
                        try {
                                Console.WriteLine ("Saving");
                                SettingsHandler.Save ();
                                parent.DoUpload ();
                        } catch (Exception e) {
                                MessageDialog md = new MessageDialog (null,
                                                                      DialogFlags.DestroyWithParent,
                                                                      MessageType.Error,
                                                                      ButtonsType.Close, "Error Saving settings\n" +
                                                                      e.ToString ());
                        }
                }

                void OnEmail_Changed (object sender, EventArgs a)
                {
                        string text = entry_email.Text;

                        if (text.IndexOf ("@") != -1 && text.Length > 3)
                                button_email_ok.Sensitive = true;
                }
        }


        class NewComment {
                [Glade.Widget] Window newcomment;
                [Glade.Widget] Entry entry;
                static NewComment NewCommentBox;
                Browser parent;

                NewComment (Browser browser)
                {
                        Glade.XML ui = new Glade.XML (null, "browser.glade", "newcomment", null);
                        ui.Autoconnect (this);
                        parent = browser;
                        newcomment.TransientFor = browser.window1;
                }

                void OnOkClicked (object sender, EventArgs a)
                {
                        CommentService service = new CommentService();
                        // todo
                        newcomment.Hide ();
                }

                void OnCancelClicked (object sender, EventArgs a)
                {
                        newcomment.Hide ();
                }

                //
                // Called on the Window delete icon clicked
                //
                void OnDelete (object sender, EventArgs a)
                {
                        NewCommentBox = null;
                }

                static public void Show (Browser browser)
                {
                        if (NewCommentBox == null)
                                NewCommentBox = new NewComment (browser);
                        NewCommentBox.newcomment.Show ();
                }
        }

        void OnNewComment (object sender, EventArgs a)
        {
                NewComment.Show (this);
        }



        class Lookup {
                [Glade.Widget] Window lookup;
                [Glade.Widget] Entry entry;
                static Lookup LookupBox;
                Browser parent;

                Lookup (Browser browser)
                {
                        Glade.XML ui = new Glade.XML (null, "browser.glade", "lookup", null);
                        ui.Autoconnect (this);
                        parent = browser;
                        lookup.TransientFor = browser.window1;
                }

                void OnOkClicked (object sender, EventArgs a)
                {
                        string text = entry.Text;
                        if (text != "")
                                parent.LoadUrl (entry.Text);
                        lookup.Hide ();
                }

                //
                // Called on the Window delete icon clicked
                //
                void OnDelete(object sender, EventArgs a)
                {
                        LookupBox = null;
                }

                static public void Show (Browser browser)
                {
                        if (LookupBox == null)
                                LookupBox = new Lookup (browser);
                        LookupBox.lookup.Show ();
                }
        }

        //
        // Invoked by File/LookupURL menu entry.
        //
        void OnLookupURL (object sender, EventArgs a)
        {
                Lookup.Show (this);
        }

        //
        // Invoked by Edit/Select All menu entry.
        //
        void OnSelectAllActivate (object sender, EventArgs a)
        {
        }

        void BookmarkHandle (object obj, EventArgs args)
        {
                Menu aux = (Menu) bookmarksMenu.Submenu;
                Gtk.Widget [] a = aux.Children;
                for (int i = 3; i < aux.Children.Length; i++) {
                        if (aux.Children [i] == obj)
                                LoadUrl (((BookLink) bookList [i - 3]).Url);
                }
        }

        void refreshBookmarkMenu ()
        {
                Menu aux = (Menu) bookmarksMenu.Submenu;

                foreach (Widget w in aux.Children)
                        aux.Remove (w);


                if (bookList.Count > 0) {
                        MenuItem aux2 = new SeparatorMenuItem ();
                        aux2.Show ();
                        aux.Append (aux2);

                        for (int i = 0; i < bookList.Count; i++) {
                                aux2 = new MenuItem (((BookLink) bookList [i]).Text);
                                aux2.Activated += new EventHandler (BookmarkHandle);
                                aux2.Show ();
                                aux.Append (aux2);
                        }
                }
        }

        void OnAddBookmark (object sender, EventArgs a)
        {
//              This url is not secure to 100 percent -> tree_browser.SelectedNode.Element
//              Console.WriteLine("Example: {0} {1}", CurrentUrl, tree_browser.SelectedNode.Caption);
                bookList.Add (new BookLink (tree_browser.SelectedNode.Caption, CurrentUrl));
                refreshBookmarkMenu();
        }

        void OnEditBookmarks (object sender, EventArgs a)
        {
                BookmarkEdit.Show(this);
        }

        void OnSaveEdits (object sender, EventArgs a)
        {
                try {
                        edit_node.InnerXml = text_editor.Buffer.Text;
                } catch (Exception e) {
                        statusbar.Pop (context_id);
                        statusbar.Push (context_id, e.Message);
                        return;
                }
                EditingUtils.SaveChange (edit_url, help_tree, edit_node);
                SetMode (Mode.Viewer);

                history.ActivateCurrent ();
        }

        void OnInsertParaClicked (object sender, EventArgs a)
        {
                text_editor.Buffer.InsertAtCursor ("\n<para>\n</para>");
        }

        void OnInsertNoteClicked (object sender, EventArgs a)
        {
                text_editor.Buffer.InsertAtCursor ("\n<block subset=\"none\" type=\"note\">\n  <para>\n  </para>\n</block>");
        }

        void OnInsertExampleClicked (object sender, EventArgs a)
        {
                text_editor.Buffer.InsertAtCursor ("\n<example>\n  <code lang=\"C#\">\n  </code>\n</example>");
        }

        void OnInsertListClicked  (object sender, EventArgs a)
        {
                text_editor.Buffer.InsertAtCursor ("\n<list type=\"bullet\">\n  <item>\n    <term>First Item</term>\n  </item>\n</list>");

        }

        void OnInsertTableClicked (object sender, EventArgs a)
        {
                text_editor.Buffer.InsertAtCursor ("\n<list type=\"table\">\n  <listheader>\n    <term>Column</term>\n" +
                                                   "    <description>Description</description>\n" +
                                                   "  </listheader>\n" +
                                                   "  <item>\n" +
                                                   "    <term>Term</term>\n" +
                                                   "    <description>Description</description>\n" +
                                                   "  </item>\n" +
                                                   "</list>");
         }

        void OnInsertType (object sender, EventArgs a)
        {
                text_editor.Buffer.InsertAtCursor ("<see cref=\"T:System.Object\"/>");
        }

        void OnCancelEdits (object sender, EventArgs a)
        {
                SetMode (Mode.Viewer);
                history.ActivateCurrent ();
        }

        void EditedTextChanged (object sender, EventArgs args)
        {
                StringWriter sw = new StringWriter ();
                XmlWriter w = new XmlTextWriter (sw);

                try {
                        w.WriteStartElement ("html");
                        w.WriteStartElement ("body");

                        edit_node.InnerXml = text_editor.Buffer.Text;
                        EditingUtils.RenderEditPreview (edit_url, help_tree, edit_node, w);

                        w.WriteEndElement ();
                        w.WriteEndElement ();

                        w.Flush ();
                } catch (Exception e) {
                        statusbar.Pop (context_id);
                        statusbar.Push (context_id, e.Message);
                        return;
                }
                statusbar.Pop (context_id);
                statusbar.Push (context_id, "XML OK");
                html.OpenStream("file://", "text/html");
                html.AppendData(sw.ToString());
                html.CloseStream();
        }

        class BookmarkEdit {
                [Glade.Widget] Window bookmarks_edit;
                [Glade.Widget] Button bookmarks_delete;
                [Glade.Widget] TreeView bookmarks_treeview;
                TreeStore store;

                static BookmarkEdit BookmarkEditBox;
                Browser parent;

                BookmarkEdit (Browser browser)
                {
                        Glade.XML ui = new Glade.XML (null, "browser.glade", "bookmarks_edit", null);
                        ui.Autoconnect (this);
                        parent = browser;
                        bookmarks_edit.TransientFor = parent.window1;
                        bookmarks_delete.Sensitive = false;

                        store = new TreeStore (typeof (string), typeof (int));
                        bookmarks_treeview.AppendColumn ("name_col", new CellRendererText (), "text", 0);
                        bookmarks_treeview.Model = store;

                        Load ();
                }

                static public void Show (Browser browser)
                {
                        if (BookmarkEditBox == null)
                                BookmarkEditBox = new BookmarkEdit (browser);

                        BookmarkEditBox.Load ();
                        BookmarkEditBox.bookmarks_edit.Show ();
                }

                void OnCancelClicked (object sender, EventArgs a)
                {
                        bookmarks_edit.Hide ();
                }

                void OnDeleteClicked (object sender, EventArgs a)
                {
                        Gtk.TreeIter iter;
                        Gtk.TreeModel model;

                        if (bookmarks_treeview.Selection.GetSelected (out model, out iter))
                                parent.bookList.RemoveAt((int)model.GetValue(iter,1));

                        if (parent.bookList.Count==0)
                                bookmarks_delete.Sensitive = false;

                        Load ();
                        parent.refreshBookmarkMenu();
                }

                public void AppendItem (string text, int num)
                {
                        store.AppendValues (text, num);
                        bookmarks_delete.Sensitive = true;
                }

                public void Load ()
                {
                        store.Clear ();
                        for (int i = 0; i < parent.bookList.Count; i++)
                                AppendItem (((BookLink) parent.bookList[i]).Text, i);
                }

                //
                // Called on the Window delete icon clicked
                //
                void OnDelete (object sender, EventArgs a)
                {
                        BookmarkEditBox = null;
                }
        }
}

//
// This class implements the tree browser
//
class TreeBrowser {
        Browser browser;

        TreeView tree_view;

        TreeStore store;
        RootTree help_tree;
        TreeIter root_iter;

        //
        // This hashtable maps an iter to its node.
        //
        Hashtable iter_to_node;

        //
        // This hashtable maps the node to its iter
        //
        Hashtable node_to_iter;

        //
        // Maps a node to its TreeIter parent
        //
        Hashtable node_parent;

        public TreeBrowser (RootTree help_tree, TreeView reference_tree, Browser browser)
        {
                this.browser = browser;
                tree_view = reference_tree;
                iter_to_node = new Hashtable ();
                node_to_iter = new Hashtable ();
                node_parent = new Hashtable ();

                // Setup the TreeView
                tree_view.AppendColumn ("name_col", new CellRendererText (), "text", 0);

                // Bind events
                tree_view.RowExpanded += new Gtk.RowExpandedHandler (RowExpanded);
                tree_view.Selection.Changed += new EventHandler (RowActivated);
                tree_view.RowActivated += new Gtk.RowActivatedHandler (RowClicked);

                // Setup the model
                this.help_tree = help_tree;
                store = new TreeStore (typeof (string));

                root_iter = store.AppendValues ("Mono Documentation");
                iter_to_node [root_iter] = help_tree;
                node_to_iter [help_tree] = root_iter;
                PopulateNode (help_tree, root_iter);

                reference_tree.Model = store;
        }

        void PopulateNode (Node node, TreeIter parent)
        {
                if (node.Nodes == null)
                        return;

                TreeIter iter;
                foreach (Node n in node.Nodes){
                        iter = store.AppendValues (parent, n.Caption);
                        iter_to_node [iter] = n;
                        node_to_iter [n] = iter;
                }
        }

        Hashtable populated = new Hashtable ();

        void RowExpanded (object o, Gtk.RowExpandedArgs args)
        {
                Node result = iter_to_node [args.Iter] as Node;

                Open (result);
        }

        void RowClicked (object o, Gtk.RowActivatedArgs args)
        {
                Gtk.TreeModel model;
                Gtk.TreeIter iter;
                Gtk.TreePath path = args.Path;

                tree_view.Selection.GetSelected (out model, out iter);

                Node result = iter_to_node [iter] as Node;

                if (!tree_view.GetRowExpanded (path)) {
                        tree_view.ExpandRow (path, false);
                        Open (result);
                } else {
                        tree_view.CollapseRow (path);
                }

        }

        void Open (Node node)
        {
                if (node == null){
                        Console.Error.WriteLine ("Expanding something that I do not know about");
                        return;
                }

                if (populated.Contains (node))
                        return;

                //
                // We need to populate data on a second level
                //
                if (node.Nodes == null)
                        return;

                foreach (Node n in node.Nodes){
                        PopulateNode (n, (TreeIter) node_to_iter [n]);
                }
                populated [node] = true;
        }

        void PopulateTreeFor (Node n)
        {
                if (populated [n] == null){
                        if (n.Parent != null) {
                                OpenTree (n.Parent);
                        }
                }
                Open (n);
        }

        public void OpenTree (Node n)
        {
                PopulateTreeFor (n);

                TreeIter iter = (TreeIter) node_to_iter [n];
                TreePath path = store.GetPath (iter);
        }

        public Node SelectedNode
        {
                get {
                        Gtk.TreeIter iter;
                        Gtk.TreeModel model;

                        if (tree_view.Selection.GetSelected (out model, out iter))
                                return (Node) iter_to_node [iter];
                        else
                                return null;
                }
        }

        public void ShowNode (Node n)
        {
                if (node_to_iter [n] == null){
                        OpenTree (n);
                        if (node_to_iter [n] == null){
                                Console.Error.WriteLine ("Internal error: no node to iter mapping");
                                return;
                        }
                }

                TreeIter iter = (TreeIter) node_to_iter [n];
                TreePath path = store.GetPath (iter);

                tree_view.ExpandToPath (path);

                IgnoreRowActivated = true;
                tree_view.Selection.SelectPath (path);
                IgnoreRowActivated = false;
                tree_view.ScrollToCell (path, null, false, 0.5f, 0.0f);
        }

        class NodePageVisit : PageVisit {
                Browser browser;
                Node n;
                string url;

                public NodePageVisit (Browser browser, Node n, string url)
                {
                        if (n == null)
                                throw new Exception ("N is null");

                        this.browser = browser;
                        this.n = n;
                        this.url = url;
                }

                public override void Go ()
                {
                        string res;
                        Node x;

                        // The root tree has no help source
                        if (n.tree.HelpSource != null)
                                res = n.tree.HelpSource.GetText (url, out x);
                        else
                                res = ((RootTree)n.tree).RenderUrl (url, out x);

                        browser.Render (res, n, url);
                }
        }

        bool IgnoreRowActivated = false;

        //
        // This has to handle two kinds of urls: those encoded in the tree
        // file, which are used to quickly lookup information precisely
        // (things like "ecma:0"), and if that fails, it uses the more expensive
        // mechanism that walks trees to find matches
        //
        void RowActivated  (object sender, EventArgs a)
        {

                browser.SetMode (Browser.Mode.Viewer);

                if (IgnoreRowActivated)
                        return;

                Gtk.TreeIter iter;
                Gtk.TreeModel model;

                if (tree_view.Selection.GetSelected (out model, out iter)){
                        Node n = (Node) iter_to_node [iter];

                        string url = n.URL;
                        Node match;
                        string s;

                        if (n.tree.HelpSource != null)
                        {
                                //
                                // Try the tree-based urls first.
                                //

                                s = n.tree.HelpSource.GetText (url, out match);
                                if (s != null){
                                        ((Browser)browser).Render (s, n, url);
                                        browser.history.AppendHistory (new NodePageVisit (browser, n, url));
                                        return;
                                }
                        }

                        //
                        // Try the url resolver next
                        //
                        s = help_tree.RenderUrl (url, out match);
                        if (s != null){
                                ((Browser)browser).Render (s, n, url);
                                browser.history.AppendHistory (new Browser.LinkPageVisit (browser, url));
                                return;
                        }

                        ((Browser)browser).Render ("<h1>Unhandled URL</h1>" + "<p>Functionality to view the resource <i>" + n.URL + "</i> is not available on your system or has not yet been implemented.</p>", null, url);
                }
        }
}

//
// The index browser
//
class IndexBrowser {
        Browser browser;

        IndexReader index_reader;
        public BigList index_list;
        public MatchModel match_model;
        public BigList match_list;
        IndexEntry current_entry = null;


        public static IndexBrowser MakeIndexBrowser (Browser browser)
        {
                IndexReader ir = browser.help_tree.GetIndex ();
                if (ir == null){
                        Gtk.Label l = new Gtk.Label ("<b>No index found</b>\n\n" +
                                             "run:\n\n    monodoc --make-index\n\nto create the index");
                        l.UseMarkup = true;
                        l.Show ();
                        browser.search_box.PackStart (l);
                        return null;
                }

                return new IndexBrowser (browser, ir);
        }

        IndexBrowser (Browser parent, IndexReader ir)
        {
                browser = parent;
                index_reader = ir;

                //
                // Setup the widget
                //
                index_list = new BigList (index_reader);
                index_list.SetSizeRequest (100, 400);

                index_list.ItemSelected += new ItemSelected (OnIndexSelected);
                index_list.ItemActivated += new ItemActivated (OnIndexActivated);
                HBox box = new HBox (false, 0);
                box.PackStart (index_list, true, true, 0);
                Scrollbar scroll = new VScrollbar (index_list.Adjustment);
                box.PackEnd (scroll, false, false, 0);

                browser.search_box.PackStart (box, true, true, 0);
                box.ShowAll ();

                //
                // Setup the matches.
                //
                match_model = new MatchModel (this);
                browser.matches.Hide ();
                match_list = new BigList (match_model);
                match_list.ItemSelected += new ItemSelected (OnMatchSelected);
                match_list.ItemActivated += new ItemActivated (OnMatchActivated);
                HBox box2 = new HBox (false, 0);
                box2.PackStart (match_list, true, true, 0);
                Scrollbar scroll2 = new VScrollbar (match_list.Adjustment);
                box2.PackEnd (scroll2, false, false, 0);
                box2.ShowAll ();

                browser.matches.Add (box2);
                index_list.SetSizeRequest (100, 200);
        }

        //
        // This class is used as an implementation of the IListModel
        // for the matches for a given entry.
        // 
        public class MatchModel : IListModel {
                IndexBrowser index_browser;
                Browser browser;

                public MatchModel (IndexBrowser parent)
                {
                        index_browser = parent;
                        browser = parent.browser;
                }

                public int Rows {
                        get {
                                if (index_browser.current_entry != null)
                                        return index_browser.current_entry.Count;
                                else
                                        return 0;
                        }
                }

                public string GetValue (int row)
                {
                        Topic t = index_browser.current_entry [row];

                        // Names from the ECMA provider are somewhat
                        // ambigious (you have like a million ToString
                        // methods), so lets give the user the full name

                        // Filter out non-ecma
                        if (t.Url [1] != ':')
                                return t.Caption;

                        switch (t.Url [0]) {
                                case 'C': return t.Url.Substring (2) + " constructor";
                                case 'M': return t.Url.Substring (2) + " method";
                                case 'P': return t.Url.Substring (2) + " property";
                                case 'F': return t.Url.Substring (2) + " field";
                                case 'E': return t.Url.Substring (2) + " event";
                                default:
                                        return t.Caption;
                        }
                }

                public string GetDescription (int row)
                {
                        return GetValue (row);
                }

        }

        void ConfigureIndex (int index)
        {
                current_entry = index_reader.GetIndexEntry (index);

                if (current_entry.Count > 1){
                        browser.matches.Show ();
                        match_list.Reload ();
                        match_list.Refresh ();
                } else {
                        browser.matches.Hide ();
                }
        }

        //
        // When an item is selected from the main index list
        //
        void OnIndexSelected (int index)
        {
                ConfigureIndex (index);
                if (browser.matches.Visible == true)
                        match_list.Selected = 0;
        }

        void OnIndexActivated (int index)
        {
                if (browser.matches.Visible == false)
                        browser.LoadUrl (current_entry [0].Url);
        }

        void OnMatchSelected (int index)
        {
        }

        void OnMatchActivated (int index)
        {
                browser.LoadUrl (current_entry [index].Url);
        }

        int FindClosest (string text)
        {
                int low = 0;
                int top = index_reader.Rows-1;
                int high = top;
                bool found = false;
                int best_rate_idx = Int32.MaxValue, best_rate = -1;

                while (low <= high){
                        int mid = (high + low) / 2;

                        //Console.WriteLine ("[{0}, {1}] -> {2}", low, high, mid);

                        string s;
                        int p = mid;
                        for (s = index_reader.GetValue (mid); s [0] == ' ';){
                                if (p == high){
                                        if (p == low){
                                                if (best_rate_idx != Int32.MaxValue){
                                                        //Console.WriteLine ("Bestrated: "+best_rate_idx);
                                                        //Console.WriteLine ("Bestrated: "+index_reader.GetValue(best_rate_idx));
                                                        return best_rate_idx;
                                                } else {
                                                        //Console.WriteLine ("Returning P="+p);
                                                        return p;
                                                }
                                        }

                                        high = mid;
                                        break;
                                }

                                if (p < 0)
                                        return 0;

                                s = index_reader.GetValue (++p);
                                //Console.WriteLine ("   Advancing to ->"+p);
                        }
                        if (s [0] == ' ')
                                continue;

                        int c, rate;
                        c = Rate (text, s, out rate);
                        //Console.WriteLine ("[{0}] Text: {1} at {2}", text, s, p);
                        //Console.WriteLine ("     Rate: {0} at {1}", rate, p);
                        //Console.WriteLine ("     Best: {0} at {1}", best_rate, best_rate_idx);
                        //Console.WriteLine ("     {0} - {1}", best_rate, best_rate_idx);
                        if (rate >= best_rate){
                                best_rate = rate;
                                best_rate_idx = p;
                        }
                        if (c == 0)
                                return mid;

                        if (low == high){
                                //Console.WriteLine ("THISPATH");
                                if (best_rate_idx != Int32.MaxValue)
                                        return best_rate_idx;
                                else
                                        return low;
                        }

                        if (c < 0){
                                high = mid;
                        } else {
                                if (low == mid)
                                        low = high;
                                else
                                        low = mid;
                        }
                }

                //              Console.WriteLine ("Another");
                if (best_rate_idx != Int32.MaxValue)
                        return best_rate_idx;
                else
                        return high;

        }

        int Rate (string user_text, string db_text, out int rate)
        {
                int c = String.Compare (user_text, db_text, true);
                if (c == 0){
                        rate = 0;
                        return 0;
                }

                int i;
                for (i = 0; i < user_text.Length; i++){
                        if (db_text [i] != user_text [i]){
                                rate = i;
                                return c;
                        }
                }
                rate = i;
                return c;
        }

        public void SearchClosest (string text)
        {
                index_list.Selected = FindClosest (text);
        }

        public void LoadSelected ()
        {
                if (browser.matches.Visible == true) {
                        if (match_list.Selected != -1)
                                OnMatchActivated (match_list.Selected);
                } else {
                        if (index_list.Selected != -1)
                                OnIndexActivated (index_list.Selected);
                }
        }
}
}