/*
	$Id: MainFormUsersPanel.cs 25 2010-01-17 19:55:07Z catwalk $
*/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Xml.Linq;
using Hiyoko.Net;
using Hiyoko.Net.Twitter;
using Hiyoko.Utilities;

namespace Hiyoko.Forms{
	using WinForms = System.Windows.Forms;
	using Gdi = System.Drawing;
	
	public partial class MainFormUsersPanel : UserControl{
		private Semaphore refreshListSemaphore = new Semaphore(1, 1);
		private UsersSource currentSource = UsersSource.None;
		private PagerActions pagerActions = null;
		private ObservableCollection<User> users = new ObservableCollection<User>();
		private object currentState = null;
		
		public MainFormUsersPanel(){
			this.InitializeComponent();
			
			this.userList.DataContext = this.users;
			
			this.usersSourceComboBox.SelectionChanged += this.UsersSourceComboBox_SelectionChanged;
			this.searchTextBox.TextChanged += this.SearchTextBox_TextChanged;
		}
		
		#region イベント
		
		private void UsersSourceComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e){
			this.pagerActions = null;
			this.CurrentPage = 1;
			this.users.Clear();
		}
		
		private void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e){
			this.pagerActions = null;
		}
		
		#endregion
		
		#region コマンド
		
		private void CreateBlock_CanExecute(object target, CanExecuteRoutedEventArgs e){
			e.CanExecute = (this.userList.SelectedValue != null);
		}
		
		private void CreateBlock_Executed(object target, ExecutedRoutedEventArgs e){
			if(this.userList.SelectedItems.Count > 0){
				this.CreateBlock(this.userList.SelectedItems.Cast<User>());
			}
		}
		
		private void DestroyBlock_CanExecute(object target, CanExecuteRoutedEventArgs e){
			e.CanExecute = (this.userList.SelectedValue != null);
		}
		
		private void DestroyBlock_Executed(object target, ExecutedRoutedEventArgs e){
			if(this.userList.SelectedItems.Count > 0){
				this.DestroyBlock(this.userList.SelectedItems.Cast<User>());
			}
		}
		
		private void CreateFriendship_CanExecute(object target, CanExecuteRoutedEventArgs e){
			e.CanExecute = (this.userList.SelectedValue != null);
		}
		
		private void CreateFriendship_Executed(object target, ExecutedRoutedEventArgs e){
			if(this.userList.SelectedItems.Count > 0){
				this.CreateFriendship(this.userList.SelectedItems.Cast<User>());
			}
		}
		
		private void DestroyFriendship_CanExecute(object target, CanExecuteRoutedEventArgs e){
			e.CanExecute = (this.userList.SelectedValue != null);
		}
		
		private void DestroyFriendship_Executed(object target, ExecutedRoutedEventArgs e){
			if(this.userList.SelectedItems.Count > 0){
				this.DestroyFriendship(this.userList.SelectedItems.Cast<User>());
			}
		}
		
		private void Refresh_CanExecute(object target, CanExecuteRoutedEventArgs e){
			e.CanExecute = IsAvailable(this.refreshListSemaphore);
		}
		
		private void Refresh_Executed(object target, ExecutedRoutedEventArgs e){
			this.RefreshList();
		}
		
		private void FirstPage_CanExecute(object target, CanExecuteRoutedEventArgs e){
			e.CanExecute = IsAvailable(this.refreshListSemaphore) && (this.pagerActions != null) && (this.pagerActions.CanFirst);
		}
		
		private void FirstPage_Executed(object target, ExecutedRoutedEventArgs e){
			this.RefreshList(new RequireWebRequestDataCallback(delegate(object arg){
				return (WebRequestData)this.pagerActions.First(arg);
			}), (UsersSource)this.pagerActions.State);
		}
		
		private void NextPage_CanExecute(object target, CanExecuteRoutedEventArgs e){
			e.CanExecute = IsAvailable(this.refreshListSemaphore) && (this.pagerActions != null) && (this.pagerActions.CanNext);
		}
		
		private void NextPage_Executed(object target, ExecutedRoutedEventArgs e){
			this.RefreshList(new RequireWebRequestDataCallback(delegate(object arg){
				return (WebRequestData)this.pagerActions.Next(arg);
			}), (UsersSource)this.pagerActions.State);
		}
		
		private void PreviousPage_CanExecute(object target, CanExecuteRoutedEventArgs e){
			e.CanExecute = IsAvailable(this.refreshListSemaphore) && (this.pagerActions != null) && (this.pagerActions.CanPrevious);
		}
		
		private void PreviousPage_Executed(object target, ExecutedRoutedEventArgs e){
			this.RefreshList(new RequireWebRequestDataCallback(delegate(object arg){
				return (WebRequestData)this.pagerActions.Previous(arg);
			}), (UsersSource)this.pagerActions.State);
		}
		
		#endregion
		
		#region 通信
		
		public void RefreshList(){
			if(this.pagerActions == null){
				string searchWord = this.searchTextBox.Text;
				UsersSource source = (this.sourceFriendsComboBoxItem.IsSelected) ? UsersSource.Friends :
				                     (this.sourceFollowersComboBoxItem.IsSelected) ? UsersSource.Followers :
				                     (this.sourceBlocksComboBoxItem.IsSelected) ? UsersSource.Blocks :
				                     (this.sourceUserSearchComboBoxItem.IsSelected) ? UsersSource.UserSearch :
				                     UsersSource.None;
				RequireWebRequestDataCallback request =
					(source == UsersSource.Friends) ? new RequireWebRequestDataCallback(delegate(object arg){
						Account account = (Account)arg;
						this.currentState = -1m;
						this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
							this.CurrentPage = 1;
						}));
						return TwitterAPI.GetFriends(account.AccessToken, account.User.Id, -1);
					}) :
					(source == UsersSource.Followers) ? new RequireWebRequestDataCallback(delegate(object arg){
						Account account = (Account)arg;
						this.currentState = -1m;
						this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
							this.CurrentPage = 1;
						}));
						return TwitterAPI.GetFollowers(account.AccessToken, account.User.Id, -1);
					}) :
					(source == UsersSource.Blocks) ? new RequireWebRequestDataCallback(delegate(object arg){
						Account account = (Account)arg;
						this.currentState = 1;
						this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
							this.CurrentPage = 1;
						}));
						return TwitterAPI.GetBlocks(account.AccessToken, 1);
					}) :
					(source == UsersSource.UserSearch) ? new RequireWebRequestDataCallback(delegate(object arg){
						if(String.IsNullOrEmpty(searchWord)){
							return null;
						}else{
							Account account = (Account)arg;
							this.currentState = 1;
							this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
								this.CurrentPage = 1;
							}));
							return TwitterAPI.SearchUsers(account.AccessToken, searchWord, 20, 1);
						}
					}) :
					null;
				this.RefreshList(request, source);
			}else{
				this.RefreshList(new RequireWebRequestDataCallback(delegate(object arg){
					return (WebRequestData)this.pagerActions.Current(arg);
				}), this.currentSource);
			}
		}
		
		private void RefreshList(RequireWebRequestDataCallback request, UsersSource source){
			P(this.refreshListSemaphore);
			this.currentSource = source;
			string searchWord = this.searchTextBox.Text;
			Program.NetworkJobServer.EnqueueJob(
				delegate(object arg){
					this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
						Program.MainForm.Outputs.Add(new MessageOutputItem("ユーザー一覧取得中..."));
					}));
					return (WebRequestData)request(arg);
				},
				null,
				delegate(NetworkJobData data){
					try{
						IList<User> users = new List<User>();
						switch(source){
							case UsersSource.Friends:
							case UsersSource.Followers:{
								XElement xml;
								using(HttpWebResponse res = (HttpWebResponse)data.WebRequestData.WebRequest.EndGetResponse(data.AsyncResult))
								using(Stream stream = res.GetResponseStream())
								using(StreamReader reader = new StreamReader(stream, Encoding.UTF8)){
									string text = reader.ReadToEnd();
									xml = XElement.Parse(text);
								}
								
								foreach(XElement user in xml.Element("users").Elements("user")){
									users.Add(User.FromXElement(user));
								}
								
								decimal nextCursor = (decimal)xml.Element("next_cursor");
								decimal previousCursor = (decimal)xml.Element("previous_cursor");
								decimal currentCursor = (decimal)this.currentState;
								
								if(source == UsersSource.Friends){
									this.pagerActions = new PagerActions(
										(previousCursor != 0) ? new RequireWebRequestDataCallback(delegate(object arg){
											var account = (Account)arg;
											this.currentState = previousCursor;
											this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
												this.CurrentPage--;
											}));
											return TwitterAPI.GetFriends(account.AccessToken, account.User.Id, previousCursor);
										}) : null,
										(nextCursor != 0) ? new RequireWebRequestDataCallback(delegate(object arg){
											var account = (Account)arg;
											this.currentState = nextCursor;
											this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
												this.CurrentPage++;
											}));
											return TwitterAPI.GetFriends(account.AccessToken, account.User.Id, nextCursor);
										}) : null,
										new RequireWebRequestDataCallback(delegate(object arg){
											var account = (Account)arg;
											this.currentState = currentCursor;
											return TwitterAPI.GetFriends(account.AccessToken, account.User.Id, currentCursor);
										}),
										(currentCursor != -1) ? new RequireWebRequestDataCallback(delegate(object arg){
											var account = (Account)arg;
											this.currentState = -1m;
											this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
												this.CurrentPage = 1;
											}));
											return TwitterAPI.GetFriends(account.AccessToken, account.User.Id, -1);
										}) : null,
										source
									);
								}else{
									this.pagerActions = new PagerActions(
										(previousCursor != 0) ? new RequireWebRequestDataCallback(delegate(object arg){
											var account = (Account)arg;
											this.currentState = previousCursor;
											this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
												this.CurrentPage--;
											}));
											return TwitterAPI.GetFollowers(account.AccessToken, account.User.Id, previousCursor);
										}) : null,
										(nextCursor != 0) ? new RequireWebRequestDataCallback(delegate(object arg){
											var account = (Account)arg;
											this.currentState = nextCursor;
											this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
												this.CurrentPage++;
											}));
											return TwitterAPI.GetFollowers(account.AccessToken, account.User.Id, nextCursor);
										}) : null,
										new RequireWebRequestDataCallback(delegate(object arg){
											var account = (Account)arg;
											this.currentState = currentCursor;
											return TwitterAPI.GetFollowers(account.AccessToken, account.User.Id, currentCursor);
										}),
										(currentCursor != -1) ? new RequireWebRequestDataCallback(delegate(object arg){
											var account = (Account)arg;
											this.currentState = -1m;
											this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
												this.CurrentPage = 1;
											}));
											return TwitterAPI.GetFollowers(account.AccessToken, account.User.Id, -1);
										}) : null,
										source
									);
								}
								
								break;
							}
							case UsersSource.Blocks:{
								XDocument xml;
								using(HttpWebResponse res = (HttpWebResponse)data.WebRequestData.WebRequest.EndGetResponse(data.AsyncResult))
								using(Stream stream = res.GetResponseStream())
								using(StreamReader reader = new StreamReader(stream, Encoding.UTF8)){
									string text = reader.ReadToEnd();
									xml = XDocument.Parse(text);
								}
								
								foreach(XElement user in xml.Element("users").Elements("user")){
									users.Add(User.FromXElement(user));
								}
								
								int currentPage = (int)this.currentState;
								int nextPage = currentPage + 1;
								int previousPage = currentPage - 1;
								
								this.pagerActions = new PagerActions(
									(previousPage > 0) ? new RequireWebRequestDataCallback(delegate(object arg){
										var account = (Account)arg;
										this.currentState = previousPage;
										this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
											this.CurrentPage--;
										}));
										return TwitterAPI.GetBlocks(account.AccessToken, previousPage);
									}) : null,
									new RequireWebRequestDataCallback(delegate(object arg){
										var account = (Account)arg;
										this.currentState = nextPage;
										this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
											this.CurrentPage++;
										}));
										return TwitterAPI.GetBlocks(account.AccessToken, nextPage);
									}),
									new RequireWebRequestDataCallback(delegate(object arg){
										var account = (Account)arg;
										this.currentState = currentPage;
										return TwitterAPI.GetBlocks(account.AccessToken, currentPage);
									}),
									(currentPage > 1) ? new RequireWebRequestDataCallback(delegate(object arg){
										var account = (Account)arg;
										this.currentState = 1;
										this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
											this.CurrentPage = 1;
										}));
										return TwitterAPI.GetBlocks(account.AccessToken, 1);
									}) : null,
									source
								);
								break;
							}
							case UsersSource.UserSearch:{
								XDocument xml;
								using(HttpWebResponse res = (HttpWebResponse)data.WebRequestData.WebRequest.EndGetResponse(data.AsyncResult))
								using(Stream stream = res.GetResponseStream())
								using(StreamReader reader = new StreamReader(stream, Encoding.UTF8)){
									string text = reader.ReadToEnd();
									xml = XDocument.Parse(text);
								}
								
								foreach(XElement user in xml.Element("users").Elements("user")){
									users.Add(User.FromXElement(user));
								}
								
								int currentPage = (int)this.currentState;
								int nextPage = currentPage + 1;
								int previousPage = currentPage - 1;
								
								this.pagerActions = new PagerActions(
									(previousPage > 0) ? new RequireWebRequestDataCallback(delegate(object arg){
										var account = (Account)arg;
										this.currentState = previousPage;
										this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
											this.CurrentPage--;
										}));
										return TwitterAPI.SearchUsers(account.AccessToken, searchWord, 20, previousPage);
									}) : null,
									new RequireWebRequestDataCallback(delegate(object arg){
										var account = (Account)arg;
										this.currentState = nextPage;
										this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
											this.CurrentPage++;
										}));
										return TwitterAPI.SearchUsers(account.AccessToken, searchWord, 20, nextPage);
									}),
									new RequireWebRequestDataCallback(delegate(object arg){
										var account = (Account)arg;
										this.currentState = currentPage;
										this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
											this.CurrentPage = currentPage;
										}));
										return TwitterAPI.SearchUsers(account.AccessToken, searchWord, 20, currentPage);
									}),
									(currentPage > 1) ? new RequireWebRequestDataCallback(delegate(object arg){
										var account = (Account)arg;
										this.currentState = 1;
										this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
											this.CurrentPage = 1;
										}));
										return TwitterAPI.SearchUsers(account.AccessToken, searchWord, 20, 1);
									}) : null,
									source
								);
								break;
							} // case
						} // switch
						this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
							this.userList.DataContext = null;
							this.users.Clear();
							foreach(User user in users){
								this.users.Add(user);
							}
							this.userList.DataContext = this.users;
							if(this.userList.Items.Count > 0){
								this.userList.ScrollIntoView(this.userList.Items[0]);
							}
							Program.MainForm.Outputs.Add(new SuccessOutputItem("ユーザー一覧取得成功"));
						}));
					}catch(WebException ex){
						this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
							Program.MainForm.Outputs.Add(new ErrorOutputItem(String.Format("{0} :{1}", (int)ex.Status, ex.Message)));
						}));
					}catch(Exception ex){
						this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
							Program.MainForm.Outputs.Add(new ErrorOutputItem(ex.ToString()));
						}));
					}finally{
					}
				},
				delegate{
					V(this.refreshListSemaphore);
				},
				this.Account
			);
		}
		
		private void CreateFriendship(IEnumerable<User> users){
			Account account = this.Account;
			foreach(User obj in users){
				User user = obj;
				Program.NetworkJobServer.EnqueueJob(
					delegate{
						this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
							Program.MainForm.Outputs.Add(new MessageOutputItem("フォロー追加中..."));
						}));
						return TwitterAPI.CreateFriendship(account.AccessToken, user.Id);
					},
					this.WriteRequestDataCallback,
					delegate(NetworkJobData data){
						try{
							using(HttpWebResponse res = (HttpWebResponse)data.WebRequestData.WebRequest.EndGetResponse(data.AsyncResult)){
								this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
									Program.MainForm.Outputs.Add(new SuccessOutputItem("フォロー追加成功"));
								}));
							}
						}catch(WebException ex){
							this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
								Program.MainForm.Outputs.Add(new ErrorOutputItem(String.Format("{0} :{1}", (int)ex.Status, ex.Message)));
							}));
						}
					}
				);
			}
		}
		
		private void DestroyFriendship(IEnumerable<User> users){
			Account account = this.Account;
			foreach(User obj in users){
				User user = obj;
				Program.NetworkJobServer.EnqueueJob(
					delegate{
						this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
							Program.MainForm.Outputs.Add(new MessageOutputItem("フォロー削除中..."));
						}));
						return TwitterAPI.DestroyFriendship(account.AccessToken, user.Id);
					},
					this.WriteRequestDataCallback,
					delegate(NetworkJobData data){
						try{
							using(HttpWebResponse res = (HttpWebResponse)data.WebRequestData.WebRequest.EndGetResponse(data.AsyncResult)){
								this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
									Program.MainForm.Outputs.Add(new SuccessOutputItem("フォロー削除成功"));
								}));
							}
						}catch(WebException ex){
							this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
								Program.MainForm.Outputs.Add(new ErrorOutputItem(String.Format("{0} :{1}", (int)ex.Status, ex.Message)));
							}));
						}
					}
				);
			}
		}
		
		private void CreateBlock(IEnumerable<User> users){
			Account account = this.Account;
			foreach(User obj in users){
				User user = obj;
				Program.NetworkJobServer.EnqueueJob(
					delegate{
						this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
							Program.MainForm.Outputs.Add(new MessageOutputItem("ブロック追加中..."));
						}));
						return TwitterAPI.CreateBlock(account.AccessToken, user.Id);
					},
					this.WriteRequestDataCallback,
					delegate(NetworkJobData data){
						try{
							using(HttpWebResponse res = (HttpWebResponse)data.WebRequestData.WebRequest.EndGetResponse(data.AsyncResult)){
								this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
									Program.MainForm.Outputs.Add(new SuccessOutputItem("ブロック追加成功"));
								}));
							}
						}catch(WebException ex){
							this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
								Program.MainForm.Outputs.Add(new ErrorOutputItem(String.Format("{0} :{1}", (int)ex.Status, ex.Message)));
							}));
						}
					}
				);
			}
		}
		
		private void DestroyBlock(IEnumerable<User> users){
			Account account = this.Account;
			foreach(User obj in users){
				User user = obj;
				Program.NetworkJobServer.EnqueueJob(
					delegate{
						this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
							Program.MainForm.Outputs.Add(new MessageOutputItem("ブロック削除中..."));
						}));
						return TwitterAPI.DestroyBlock(account.AccessToken, user.Id);
					},
					this.WriteRequestDataCallback,
					delegate(NetworkJobData data){
						try{
							using(HttpWebResponse res = (HttpWebResponse)data.WebRequestData.WebRequest.EndGetResponse(data.AsyncResult)){
								this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
									Program.MainForm.Outputs.Add(new SuccessOutputItem("ブロック削除成功"));
								}));
							}
						}catch(WebException ex){
							this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
								Program.MainForm.Outputs.Add(new ErrorOutputItem(String.Format("{0} :{1}", (int)ex.Status, ex.Message)));
							}));
						}
					}
				);
			}
		}
		
		private void WriteRequestDataCallback(NetworkJobData data){
			try{
				using(Stream stream = data.WebRequestData.WebRequest.EndGetRequestStream(data.AsyncResult)){
					data.WebRequestData.WriteRequestData(stream);
				}
			}catch(WebException ex){
				this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{
					Program.MainForm.Outputs.Add(new ErrorOutputItem(String.Format("{0} :{1}", (int)ex.Status, ex.Message)));
				}));
			}
		}
		
		private enum UsersSource{
			None,
			Friends,
			Followers,
			Blocks,
			UserSearch,
		}
		
		#endregion
		
		#region プロパティ
		
		public static readonly DependencyProperty AccountProperty = DependencyProperty.Register("Account", typeof(Account), typeof(MainFormUsersPanel));
		public Account Account{
			get{
				return (Account)this.GetValue(AccountProperty);
			}
			set{
				this.SetValue(AccountProperty, value);
			}
		}
		
		public static readonly DependencyProperty CurrentPageProperty = DependencyProperty.Register("CurrentPage", typeof(int), typeof(MainFormUsersPanel));
		public int CurrentPage{
			get{
				return (int)this.GetValue(CurrentPageProperty);
			}
			set{
				if(value <= 0){
					throw new ArgumentOutOfRangeException();
				}
				this.SetValue(CurrentPageProperty, value);
			}
		}
		
		#endregion
		
		#region セマフォ
		
		private static bool IsAvailable(Semaphore sem){
			bool signal = sem.WaitOne(0, false);
			if(signal){
				sem.Release();
			}
			return signal;
		}
		
		private static void P(Semaphore sem){
			sem.WaitOne();
			Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.SystemIdle, new Action(delegate{
				CommandManager.InvalidateRequerySuggested();
			}));
		}
		
		private static void V(Semaphore sem){
			sem.Release();
			Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.SystemIdle, new Action(delegate{
				CommandManager.InvalidateRequerySuggested();
			}));
		}
		
		#endregion
	}
}