context: //
  // demo.cs
  //
  // Authors:
  //   Miguel de Icaza (miguel.de.icaza@gmail.com)
  //
  // Copyright (C) 2007 Novell (http://www.novell.com)
  //
  // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to
  // permit persons to whom the Software is furnished to do so, subject to
  // the following conditions:
  // 
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  // 
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  //
  
  using System;
  using System.Collections.Generic;
  using System.IO;
  using System.Collections;
  using System.Xml;
  using System.Xml.Serialization;
  using System.Threading;
  
  using Mono.Terminal;
  
  using MonoTorrent.Common;
  using MonoTorrent.Client;
  using MonoTorrent.BEncoding;
  
  public class TorrentCurses {
  
  	//
  	// Configuration data, loaded at startup
  	//
  	static string config_file = Path.Combine (Environment.GetFolderPath (
  							  Environment.SpecialFolder.ApplicationData), "monotorrent.config");
  	public static string fast_resume = Path.Combine (Environment.GetFolderPath (
  							  Environment.SpecialFolder.ApplicationData), "monotorrent.fastresume");
  
  	static Config config;
  
  	// The torrent status
  	static TorrentList torrent_list;
  	static TorrentDetailsList details_list;
  	static EngineSettings engine_settings;
  	static TorrentSettings torrent_settings;
  	public static ClientEngine engine;
  	static ListView list_details;
  	static LogWidget log_widget;
  	
  	static Label iteration;
  
  	public class TorrentList : IListProvider {
  		public List<TorrentManager> items = new List<TorrentManager> ();
  		public ListView view;
  		private List<FastResume> fastResume;
  
  		void IListProvider.SetListView (ListView v)
  		{
  			view = v;
  		}
  		
  		int IListProvider.Items {
  			get {
  				return items.Count;
  			}
  		}
  
  		public TorrentList ()
  		{
  			fastResume = new List<FastResume>();
  			LoadFastResume ();
  		}
  		
  		public void SaveFastResume ()
  		{
  			
  			BEncodedDictionary dict = new BEncodedDictionary();
  			foreach (TorrentManager manager in items)
  				dict.Add (manager.Torrent.InfoHash, manager.SaveFastResume ().Encode ());
  			try
  			{
  				File.WriteAllBytes (TorrentCurses.fast_resume, dict.Encode ());
  			}
  			catch
  			{
  			}
  		}
  		void LoadFastResume()
  		{
  			fastResume = new List<FastResume>();
  			
  			try
  			{
  				if (!File.Exists (fast_resume))
  					return;
  				BEncodedDictionary dict = BEncodedValue.Decode<BEncodedDictionary>(File.ReadAllBytes(fast_resume));
  				
  				foreach (BEncodedDictionary v in dict.Values)
  					fastResume.Add (new FastResume(v));
  				
  				File.Delete (fast_resume);
  			}
  			catch
  			{
  			}
  		}
  		
  		public void Add (string s)
  		{
  			if (!File.Exists (s))
  				return;
  
  			foreach (TorrentManager mgr in items){
  				if (mgr.Torrent.TorrentPath == s){
  					Application.Info ("Info", "The specified torrent is already loaded");
  					continue;
  				}
  			}
  			Torrent torrent = Torrent.Load(s);
  			FastResume resume = null;
  			foreach (FastResume r in fastResume)
  			{
  				if (((BEncodedString)r.InfoHash).Equals((BEncodedString)torrent.InfoHash))
  					resume = r;
  			}
  			TorrentManager manager;
  			if (resume == null)
  				manager = new TorrentManager(torrent, engine_settings.SavePath, (TorrentSettings)torrent_settings.Clone());
  			else
  				manager = new MonoTorrent.Client.TorrentManager(torrent, engine_settings.SavePath, (TorrentSettings)torrent_settings.Clone(), resume);
  			
  			manager.PeerConnected +=
  				new EventHandler<PeerConnectionEventArgs>(cm_PeerConnected);
  			manager.PeerDisconnected +=
  			new EventHandler<PeerConnectionEventArgs>(cm_PeerDisconnected);
  			engine.Register(manager);
  			items.Add (manager);
  			
  			if (view != null)
  				view.ProviderChanged ();
  
  			manager.Start();
  		}
  		
  		bool IListProvider.AllowMark {
  			get {
  				return false;
  			}
  		}
  
  		bool IListProvider.IsMarked (int n)
  		{
  			return false;
  		}
  
  		void IListProvider.Render (int line, int col, int width, int item)
  		{
  			TorrentManager manager = items [item];
  			string name = manager.Torrent.Name;
  			
  			string s = String.Format ("[{0,3:N0}%] {1}", manager.Progress, name);
  			if (s.Length > width){
  				s = s.Substring (0, width);
  				Curses.addstr (s);
  			} else {
  				Curses.addstr (s);
  				for (int i = s.Length; i < width; i++)
  					Curses.addch (' ');
  			}
  		}
  
  		bool IListProvider.ProcessKey (int ch)
  		{
  			if (ch != '\n')
  				return false;
  
  			int selected = view.Selected;
  			if (selected == -1)
  				return false;
  
  			TorrentControl (selected);
  			return true;
  		}
  
  		void IListProvider.SelectedChanged ()
  		{
  			UpdateStatus ();
  
  			list_details.ProviderChanged ();
  		}
  		
  		public void TorrentControl (int selected)
  		{
  			Dialog d = new Dialog (60, 8, "Torrent Control");
  
  			TorrentManager item = items [selected];
  			
  			d.Add (new TrimLabel (1, 1, 60-6, item.Torrent.Name));
  
  			bool stopped = item.State == TorrentState.Stopped;
  			Button bstartstop = new Button (stopped ? "Start" : "Stop");
  			bstartstop.Clicked += delegate {
  				if (stopped)
  					item.Start();
  				else
  					item.Stop();
  				d.Running = false;
  				
  			};
  			d.AddButton (bstartstop);
  			
  			// Later, when we hook it up, look up the state
  			
  			bool paused = item.State == TorrentState.Paused;
  			Button br = new Button (paused ? "Resume" : "Pause");
  			br.Clicked += delegate {
  				if (paused)
  					item.Start();
  				else
  					item.Pause();
  				d.Running = false;
  			};
  			d.AddButton (br);
  
  			Button bd = new Button ("Delete");
  			bd.Clicked += delegate {
  				Application.Error ("Not Implemented",
  						   "I have not implemented delete yet");
  				d.Running = false;
  			};
  			d.AddButton (bd);
  			
  			Button bmap = new Button ("Map");
  			bmap.Clicked += delegate {
  				Application.Error ("Not Implemented",
  						   "I have not implemented map yet");
  				d.Running = false;
  			};
  			d.AddButton (bmap);
  
  			Button bcancel = new Button ("Cancel");
  			bcancel.Clicked += delegate {
  				d.Running = false;
  			};
  			Application.Run (d);
  			UpdateStatus ();
  		}
  
  		public ArrayList GetPathNames ()
  		{
  			ArrayList names = new ArrayList ();
  			
  			foreach (TorrentManager tm in items){
  				TorrentDesc td = new TorrentDesc ();
  				td.Filename = tm.Torrent.TorrentPath;
  				names.Add (td);
  			}
  
  			return names;
  		}
  
  		public TorrentManager GetSelected ()
  		{
  			int selected = view.Selected;
  			if (selected == -1)
  				return null;
  
  			return items [selected];
  		}
  	}
  
  	public class TorrentDetailsList : IListProvider {
  		public ListView view;
  		
  		void IListProvider.SetListView (ListView v)
  		{
  			view = v;
  		}
  		
  		int IListProvider.Items {
  			get {
  				TorrentManager tm = torrent_list.GetSelected ();
  
  				if (tm == null)
  					return 0;
  
  				return tm.Torrent.Files.Length;
  			}
  		}
  
  		bool IListProvider.AllowMark {
  			get {
  				return false;
  			}
  		}
  
  		bool IListProvider.IsMarked (int n)
  		{
  			return false;
  		}
  
  		void IListProvider.Render (int line, int col, int width, int item)
  		{
  			TorrentManager tm = torrent_list.GetSelected ();
  			string name;
  			
  			if (tm == null)
  				name = "";
  			else 
  				name = tm.Torrent.Files [item].Path;
  
  			string s = String.Format ("{0}. {1}", item, name);
  			if (s.Length > width){
  				s = s.Substring (0, width);
  				Curses.addstr (s);
  			} else {
  				Curses.addstr (s);
  				for (int i = s.Length; i < width; i++)
  					Curses.addch (' ');
  			}
  		}
  
  		bool IListProvider.ProcessKey (int ch)
  		{
  			return false;
  		}
  
  		void IListProvider.SelectedChanged ()
  		{
  		}
  	}
  
  	public class LogWidget : Widget {
  		string [] messages = new string [80];
  		int head, tail;
  		int count;
  		
  		public LogWidget (int x, int y) : base (x, y, 0, 0)
  		{
  			Fill = Fill.Horizontal | Fill.Vertical;
  			AddText ("Started");
  		}
  
  		public void AddText (string s)
  		{
  			messages [head] = s;
  			head++;
  			if (head == messages.Length)
  				head = 0;
  			if (head == tail)
  				tail = (tail+1) % messages.Length;
  		}
  		
  		public override void Redraw ()
  		{
  			Curses.attrset (ColorNormal);
  
  			int i = 0;
  			int l;
  			int n = head > tail ? head-tail : (head + messages.Length) - tail;
  			for (l = h-1; l >= 0 && n-- > 0; l--){
  				int item = head-1-i;
  				if (item < 0)
  					item = messages.Length+item;
  
  				Move (y+l, x);
  
  				int sl = messages [item].Length;
  				if (sl < w){
  
gt: 					Curses.addstr (messages [item]);