using System;
using System.Collections;
using System.Drawing;
using System.Data;
using System.Data.SqlClient;

using Gtk;

enum DialogType
{
	Insert,
	Delete,
	Update
}

class Client {

	static Window window;
	static Dialog dialog;
	static Toolbar toolbar;
	static Table tableau;
	static Entry id_entry;
	static Entry name_entry;
	static Entry address_entry;
	static Statusbar status;
	static Stack statusIds;
	static VBox box;
	static IdConnection conn;
	static bool noConn;
	
	static void Main ()
	{
		Application.Init ();
		window = new Window ("Database client");
		window.DeleteEvent += new DeleteEventHandler (Window_Delete);
		window.DefaultSize = new Size (300, 200);

		box = new VBox (false, 0);
		window.Add (box);

		box.PackStart (CreateMenu (), false, false, 0);
		toolbar = new Toolbar ();
		PackToolbar ();
		box.PackStart (toolbar, false, false, 0);

		UpdateView ();

		status = new Statusbar ();
		box.PackEnd (status, false, false, 0);
		window.ShowAll ();
		Application.Run ();
	}

	static uint context_id = 0;
	static void PushMessage (string message)
	{
		if (statusIds == null)
			statusIds = new Stack ();
		if (status != null)
			statusIds.Push (status.Push (context_id++, message));
	}

	static void PopMessage ()
	{
		if (statusIds == null || statusIds.Count == 0)
			return;
		status.Pop ((uint) statusIds.Pop ());
	}
	
	static void PackToolbar ()
	{
		toolbar.AppendItem ("Insert", "Insert a row", String.Empty,
				    new Gtk.Image (Stock.Add, IconSize.LargeToolbar),
				    new SignalFunc (Db_Insert));
		
		toolbar.AppendItem ("Remove", "Remove a row", String.Empty,
				    new Gtk.Image (Stock.Remove, IconSize.LargeToolbar),
				    new SignalFunc (Db_Remove));

		toolbar.AppendItem ("Update", "Update a row", String.Empty,
				    new Gtk.Image (Stock.Italic, IconSize.LargeToolbar),
				    new SignalFunc (Db_Update));

		toolbar.AppendItem ("Refresh", "Refresh the view", String.Empty,
				    new Gtk.Image (Stock.Refresh, IconSize.LargeToolbar),
				    new SignalFunc (UpdateView));

		toolbar.AppendSpace ();		

		toolbar.InsertStock (Stock.Quit, "Quit", String.Empty,
				     new Gtk.SignalFunc (Quit), IntPtr.Zero, -1);

		toolbar.ToolbarStyle = ToolbarStyle.BothHoriz;
	}

	static void Window_Delete (object o, DeleteEventArgs args)
	{
		Application.Quit ();
		args.RetVal = true;
	}

	static void UpdateView ()
	{
		if (tableau != null)
			tableau.Destroy ();

		PopMessage ();
		PushMessage ("");
		ArrayList dataList = null;
		try {
			dataList = Conn.SelectAll ();
		} catch (Exception e) {
			Console.WriteLine ("Error accesing DB.\n");
			Console.WriteLine (e.Message);
			Console.Write ("\nYou should set up a PostgreSQL database for user 'monotest', ");
			Console.Write ("password\n'monotest' and dbname 'monotest' accesible ");
			Console.WriteLine ("through the loopback interface.\n");
			Console.WriteLine ("The database should have a table called customers " + 
					   "created with the following\ncommand:\n");
			Console.WriteLine ("CREATE TABLE \"customers\" (");
			Console.WriteLine ("\t\"id\" integer NOT NULL,");
			Console.WriteLine ("\t\"name\" character varying(256) NOT NULL,");
			Console.WriteLine ("\t\"address\" character varying(256) NOT NULL");
			Console.WriteLine (");\n");
			Console.WriteLine ("CREATE UNIQUE INDEX id_idx ON customers USING btree (id);\n");

			Environment.Exit (1);
		}
		
		tableau = new Gtk.Table ((uint) dataList.Count + 1, 3, false);
		DrawTitles (tableau);
		tableau.ColumnSpacing = 10;
		uint i = 1;
		foreach (Record r in dataList) {
			tableau.Attach (new Label (r.ID.ToString ()), 0, 1, i, i + 1);
			tableau.Attach (new Label (r.Name), 1, 2, i, i + 1);
			tableau.Attach (new Label (r.Address), 2, 3, i, i + 1);
			i++;
		}
		
		tableau.Show ();
		box.PackStart (tableau, false, false, 0);
		box.ShowAll ();
	}

	static void DrawTitles (Gtk.Table t)
	{
		Label label = null;

		label = new Label (String.Empty);
		label.Markup = "<big><b>ID</b></big>";
		label.UseMarkup = true;

		t.Attach (label, 0, 1, 0, 1);

		label = new Label (String.Empty);
		label.Markup = "<big><b>Name</b></big>";
		label.UseMarkup = true;
		t.Attach (label, 1, 2, 0, 1);

		label = new Label (String.Empty);
		label.Markup = "<big><b>Address</b></big>";
		label.UseMarkup = true;
		t.Attach (label, 2, 3, 0, 1);
	}

	static void Db_Insert ()
	{
		if (dialog != null) {
			return;
		}
		dialog = new Dialog ();
		dialog.Title = "Insert row";
		dialog.BorderWidth = 3;
		dialog.VBox.BorderWidth = 5;
		dialog.HasSeparator = false;

		Frame frame = new Frame ("Insert a row");
		frame.Add (MakeDialog (Stock.DialogInfo, DialogType.Insert));
		dialog.VBox.PackStart (frame, true, true, 0);

		Button button = null;
		button = new Button (Stock.Add);
		button.Clicked += new EventHandler (Insert_Action);
		button.CanDefault = true;
		dialog.ActionArea.PackStart (button, true, true, 0);
		button.GrabDefault ();

		button = new Button (Stock.Cancel);
		button.Clicked += new EventHandler (Dialog_Cancel);
		dialog.ActionArea.PackStart (button, true, true, 0);
		dialog.Modal = true;

		dialog.ShowAll ();
	}

	static void Db_Remove ()
	{
		if (dialog != null) {
			return;
		}
		dialog = new Dialog ();
		dialog.Title = "Remove row";
		dialog.BorderWidth = 3;		
		dialog.VBox.BorderWidth = 5;
		dialog.HasSeparator = false;

		Frame frame = new Frame ("Remove a row");
		frame.Add (MakeDialog (Stock.DialogWarning, DialogType.Delete));
		dialog.VBox.PackStart (frame, true, true, 0);

		Button button = null;
		button = new Button (Stock.Remove);
		button.Clicked += new EventHandler (Remove_Action);
		button.CanDefault = true;
		dialog.ActionArea.PackStart (button, true, true, 0);
		button.GrabDefault ();

		button = new Button (Stock.Cancel);
		button.Clicked += new EventHandler (Dialog_Cancel);
		dialog.ActionArea.PackStart (button, true, true, 0);

		dialog.ShowAll ();

	}

	static Widget MakeDialog (string image, DialogType type)
	{
		HBox hbox = new HBox (false, 2);
		hbox.BorderWidth = 5;
		hbox.PackStart (new Gtk.Image (image, IconSize.Dialog), true, true, 0);
		
		Table table = new Table (3, 3, false);
		hbox.PackStart (table);
		table.ColumnSpacing = 4;
		table.RowSpacing = 4;
		Label label = null;

		label = new Label ("_ID");
		table.Attach (label, 0, 1, 0, 1);
		id_entry = new Entry ();
		table.Attach (id_entry, 1, 2, 0, 1);

		label = new Label ("_Name");
		table.Attach (label, 0, 1, 1, 2);
		name_entry = new Entry ();
		if (type == DialogType.Delete)
			name_entry.Sensitive = false;
		table.Attach (name_entry, 1, 2, 1, 2);

		label = new Label ("_Address");
		table.Attach (label, 0, 1, 2, 3);
		address_entry = new Entry ();
		if (type == DialogType.Delete)
			address_entry.Sensitive = false;
		table.Attach (address_entry, 1, 2, 2, 3);

		return hbox ;
	}

	static void Db_Update ()
	{
		if (dialog != null) {
			return;
		}
		dialog = new Dialog ();
		dialog.Title = "Update row";
		dialog.BorderWidth = 3;		
		dialog.VBox.BorderWidth = 5;
		dialog.HasSeparator = false;

		Frame frame = new Frame ("Update row");
		frame.Add (MakeDialog (Stock.DialogWarning, DialogType.Update));
		dialog.VBox.PackStart (frame, true, true, 0);

		Button button = null;
		button = new Button (Stock.Apply);
		button.Clicked += new EventHandler (Update_Action);
		button.CanDefault = true;
		dialog.ActionArea.PackStart (button, true, true, 0);
		button.GrabDefault ();

		button = new Button (Stock.Cancel);
		button.Clicked += new EventHandler (Dialog_Cancel);
		dialog.ActionArea.PackStart (button, true, true, 0);

		dialog.ShowAll ();
	}

	static void Quit ()
	{
		Application.Quit ();
	}

	static void Insert_Action (object o, EventArgs args)
	{
		try {
			Conn.Insert (UInt32.Parse (id_entry.Text), name_entry.Text, address_entry.Text);
			UpdateView ();
		} catch (Exception e) {
			PushMessage (e.Message);
		}
		dialog.Destroy ();
		dialog = null;
	}

	static void Remove_Action (object o, EventArgs args)
	{
		try {
			Conn.Delete (UInt32.Parse (id_entry.Text));
			UpdateView ();
		} catch (Exception e) {
			PushMessage (e.Message);
		}
		dialog.Destroy ();
		dialog = null;
	}

	static void Update_Action (object o, EventArgs args)
	{
		try {
			Conn.Update (UInt32.Parse (id_entry.Text), name_entry.Text, address_entry.Text);
			UpdateView ();
		} catch (Exception e) {
			PushMessage (e.Message);
		}
		dialog.Destroy ();
		dialog = null;
	}

	static void Dialog_Cancel (object o, EventArgs args)
	{
		dialog.Destroy ();
		dialog = null;
	}

	static Gtk.MenuBar CreateMenu ()
	{
		MenuBar mb = new MenuBar ();
		Menu file_menu = new Menu ();		
		
		ImageMenuItem quit_item = new ImageMenuItem ("Quit");
		quit_item.Image = new Gtk.Image (Stock.Quit, IconSize.Menu);
		quit_item.Activated += new EventHandler (Quit_Activated);

		file_menu.Append (new SeparatorMenuItem ());
		file_menu.Append (quit_item);
	
		MenuItem file_item = new MenuItem ("_File");
		file_item.Submenu = file_menu;
		mb.Append (file_item);

		Menu action_menu = new Menu ();
		MenuItem action_item = new MenuItem ("_Action");
		action_item.Submenu = action_menu;
		mb.Append (action_item);

		ImageMenuItem insert_item = new ImageMenuItem ("Insert");
		insert_item.Image = new Gtk.Image (Stock.Add, IconSize.Menu);
		insert_item.Activated += new EventHandler (Insert_Activated);
		action_menu.Append (insert_item);
		
		ImageMenuItem remove_item = new ImageMenuItem ("Remove");
		remove_item.Image = new Gtk.Image (Stock.Remove, IconSize.Menu);
		remove_item.Activated += new EventHandler (Remove_Activated);
		action_menu.Append (remove_item); 

		ImageMenuItem update_item = new ImageMenuItem ("Update");
		update_item.Image = new Gtk.Image (Stock.Italic, IconSize.Menu);
		update_item.Activated += new EventHandler (Update_Activated);
		action_menu.Append (update_item);

		Menu help_menu = new Menu ();
		MenuItem help_item = new MenuItem ("_Help");
		help_item.Submenu = help_menu;
		MenuItem about = new MenuItem ("About");
		about.Activated += new EventHandler (About_Box);
		help_menu.Append (about);
		mb.Append (help_item);

		return mb;
	}

	static void Insert_Activated (object o, EventArgs args)
	{
		Db_Insert ();
	}

	static void Remove_Activated (object o, EventArgs args)
	{
		Db_Remove ();
	}

	static void Update_Activated (object o, EventArgs args)
	{
		Db_Update ();
	}

	static void Quit_Activated (object o, EventArgs args)
	{
		Application.Quit ();
	}

	static void About_Box (object o, EventArgs args)
	{
		string [] authors = new string [] {
			"Gonzalo Paniagua (gonzalo@ximian.com)",
			"Duncan Mak (duncan@ximian.com)",
		};

		string [] documenters = new string [] {};
		Gdk.Pixbuf pixbuf = new Gdk.Pixbuf ("../pixmaps/gtk-sharp-logo.png");

		Gnome.About about = new Gnome.About ("Database Client", "0.1",
						     "Copyright (C) 2002, Ximian Inc.",
						     "A Sample Database client",
						     authors, documenters, "", pixbuf);

		about.Show ();
	}

	
	static IdConnection Conn
	{
		get {
			if (conn == null)
				conn = new IdConnection ();
			return conn;
		}
	}
}

struct Record {
	public uint ID;
	public string Name;
	public string Address;

	public Record (uint i, string s, string t)
	{
		ID = i;
		Name = s;
		Address = t;
	}
}

class IdConnection : IDisposable
{
	private SqlConnection cnc;
	private bool disposed;

	public IdConnection ()
	{
		cnc = new SqlConnection ();
		string connectionString = "hostaddr=127.0.0.1;" +
					  "user=monotest;" +
					  "dbname=monotest";
						  
		cnc.ConnectionString = connectionString;
		try {
			cnc.Open ();
		} catch (Exception){
			cnc = null;
			throw;
		}
	}

	public void Insert (uint id, string name, string address)
	{
		string insertCmd = String.Format ("INSERT INTO customers VALUES ({0}, '{1}', '{2}')",
						   id, name.Trim (), address.Trim ());
		IDbCommand insertCommand = cnc.CreateCommand();
		insertCommand.CommandText = insertCmd;
		insertCommand.ExecuteNonQuery ();
	}

	public void Delete (uint id)
	{
		string deleteCmd = String.Format ("DELETE FROM customers WHERE id = {0}", id);
		IDbCommand deleteCommand = cnc.CreateCommand();
		deleteCommand.CommandText = deleteCmd;
		deleteCommand.ExecuteNonQuery ();
	}

	public bool Update (uint id, string name, string address)
	{
		string updateCmd = String.Format ("UPDATE customers SET name = '{1}', address = '{2}' WHERE id = {0}",
						   id, name.Trim (), address.Trim ());
		IDbCommand updateCommand = cnc.CreateCommand();
		updateCommand.CommandText = updateCmd;
		bool updated = false;
		return (updateCommand.ExecuteNonQuery () != 0);
	}

	public ArrayList SelectAll ()
	{
		IDbCommand selectCommand = cnc.CreateCommand();
		string selectCmd = "SELECT id, name, address FROM customers ORDER by id";
		selectCommand.CommandText = selectCmd;
		IDataReader reader = selectCommand.ExecuteReader ();
		return FillDataList (reader);
	}

	public Record Select (uint id)
	{
		IDbCommand selectCommand = cnc.CreateCommand();
		string selectCmd = "SELECT id, name, address FROM customers WHERE id = " + id;
		selectCommand.CommandText = selectCmd;
		IDataReader reader = selectCommand.ExecuteReader ();
		ArrayList list = FillDataList (reader);
		return (Record) list [0];
	}

	private ArrayList FillDataList (IDataReader reader)
	{
		ArrayList list = new ArrayList ();
		while (reader.Read ()) {
			Record data = new Record (UInt32.Parse (reader.GetValue (0).ToString ()),
						  (string) reader.GetValue (1),
						  (string) reader.GetValue (2));
			list.Add (data);
		}
		return list;
	}

	protected virtual void Dispose (bool exp)
	{
		if (!disposed && cnc != null) {
			disposed = true;
			try {
				cnc.Close ();
			} catch (Exception) {
			}
			cnc = null;
		}
	}

	public void Dispose ()
	{
		Dispose (true);
		GC.SuppressFinalize (this);
	}

	~IdConnection ()
	{
		Dispose (false);
	}
}

