/*
	$Id: Settings.cs 62 2010-02-01 18:06:46Z catwalk $
*/
using System;
using System.Linq;
using System.Configuration;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Security.Cryptography;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using Hiyoko.Net.OAuth;

namespace Hiyoko{
	public static class ApplicationSettingsBaseExtension{
		public static void CopyTo(this ApplicationSettingsBase source, ApplicationSettingsBase dest){
			foreach(SettingsProperty prop in source.Properties.Cast<SettingsProperty>()
			                                                  .Where(prop => (dest[prop.Name] != source[prop.Name]))){
				dest[prop.Name] = source[prop.Name];
			}
		}
	}
	
	public abstract class UpgradeOnceApplicationSettingsBase : ApplicationSettingsBase{
		public UpgradeOnceApplicationSettingsBase(){}
		public UpgradeOnceApplicationSettingsBase(string key) : base(key){}
		
		[NoSettingsVersionUpgrade]
		[UserScopedSetting]
		[DefaultSettingValue("false")]
		public virtual bool IsUpgradedSettings{
			get{
				return (bool)this["IsUpgradedSettings"];
			}
			set{
				this["IsUpgradedSettings"] = value;
			}
		}
		
		protected override void OnSettingsLoaded(object sender, SettingsLoadedEventArgs e){
			base.OnSettingsLoaded(sender, e);
		}
		
		public void UpgradeOnce(){
			if(!this.IsUpgradedSettings){
				this.Upgrade();
				this.IsUpgradedSettings = true;
			}
		}
	}
	
	public class ApplicationSettings : UpgradeOnceApplicationSettingsBase{
		public ApplicationSettings(){}
		public ApplicationSettings(string key) : base(key){}
		
		#region Infrastructure
		
		public override void Upgrade(){
			base.Upgrade();
			var value = this.GetPreviousVersion("Version");
			if(value == null){
				this.UpgradeVersion0To1();
			}
		}
		
		private void UpgradeVersion0To1(){
			// Ver0から引き継ぎ。暗号化。
			var token = (AccessToken)this.GetPreviousVersion("AccessToken");
			if(token != null){
				this.AccessToken = token;
			}
			var uri = (Uri)this.GetPreviousVersion("WebProxyUri");
			if(uri != null){
				this.WebProxyUriString = uri.ToString();
#pragma warning disable 612
				this.WebProxyUri = null;
#pragma warning restore 612
			}
			var login = (string)this.GetPreviousVersion("BitlyLogin");
			if(login != null){
				this.BitlyLogin = login;
			}
			var apikey = (string)this.GetPreviousVersion("BitlyApiKey");
			if(apikey != null){
				this.BitlyApiKey = apikey;
			}
			var name = (string)this.GetPreviousVersion("TwitpicUsername");
			if(name != null){
				this.TwitpicUsername = name;
			}
			var password = (string)this.GetPreviousVersion("TwitpicPassword");
			if(password != null){
				this.TwitpicPassword = password;
			}
			this.Version = 1;
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("1")]
		public int Version{
			get{
				return (int)this["Version"];
			}
			set{
				this["Version"] = value;
			}
		}
		
		private static readonly byte[] optionalEntropy = new byte[]{0xfd, 0xae, 0xbc, 0xcb, 0xea, 0xdf};
		
		private static string Protect(string plain){
			if(plain == null){
				return "";
			}else{
				var data = Encoding.UTF8.GetBytes(plain);
				try{
					return Convert.ToBase64String(ProtectedData.Protect(data, optionalEntropy, DataProtectionScope.CurrentUser));
				}catch{
					return plain;
				}
			}
		}
		
		private static string Unprotect(string encrypted){
			if(encrypted == null){
				return "";
			}else{
				var data = Convert.FromBase64String(encrypted);
				try{
					return Encoding.UTF8.GetString(ProtectedData.Unprotect(data, optionalEntropy, DataProtectionScope.CurrentUser));
				}catch{
					return encrypted;
				}
			}
		}
		
		#endregion
		
		#region Protected
		
		[UserScopedSetting]
		[DefaultSettingValue(null)]
		public string WebProxyUriString{
			get{
				return Unprotect((string)this["WebProxyUriString"]);
			}
			set{
				this["WebProxyUriString"] = Protect(value);
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("")]
		public string BitlyLogin{
			get{
				return Unprotect((string)this["BitlyLogin"]);
			}
			set{
				this["BitlyLogin"] = Protect(value);
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("")]
		public string BitlyApiKey{
			get{
				return Unprotect((string)this["BitlyApiKey"]);
			}
			set{
				this["BitlyApiKey"] = Protect(value);
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("")]
		public string TwitpicUsername{
			get{
				return Unprotect((string)this["TwitpicUsername"]);
			}
			set{
				this["TwitpicUsername"] = Protect(value);
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("")]
		public string TwitpicPassword{
			get{
				return Unprotect((string)this["TwitpicPassword"]);
			}
			set{
				this["TwitpicPassword"] = Protect(value);
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue(null)]
		[SettingsSerializeAs(SettingsSerializeAs.Binary)]
		public AccessToken AccessToken{
			get{
				var token = (AccessToken)this["AccessToken"];
				return (token != null) ? new AccessToken(Unprotect(token.TokenValue), Unprotect(token.TokenSecret)) : token;
			}
			set{
				this["AccessToken"] = (value != null) ? new AccessToken(Protect(value.TokenValue), Protect(value.TokenSecret)) : value;
			}
		}
		
		#endregion
		
		#region Update
		
		[UserScopedSetting]
		[DefaultSettingValue("true")]
		public bool IsAutoCheckUpdates{
			get{
				return (bool)this["IsAutoCheckUpdates"];
			}
			set{
				this["IsAutoCheckUpdates"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("")]
		public DateTime LastCheckedUpdatesDate{
			get{
				return (DateTime)this["LastCheckedUpdatesDate"];
			}
			set{
				this["LastCheckedUpdatesDate"] = value;
			}
		}
		
		#endregion
		
		#region 設定
		
		[UserScopedSetting]
		[DefaultSettingValue(null)]
		public string StartupScriptPath{
			get{
				return (string)this["StartupScriptPath"];
			}
			set{
				this["StartupScriptPath"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("15000")]
		public int ConnectionTimeout{
			get{
				return (int)this["ConnectionTimeout"];
			}
			set{
				this["ConnectionTimeout"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("yyyy-MM-dd ddd hh-mm-ss tt")]
		public string DateTimeFormat{
			get{
				return (string)this["DateTimeFormat"];
			}
			set{
				this["DateTimeFormat"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("true")]
		public bool IsResident{
			get{
				return (bool)this["IsResident"];
			}
			set{
				this["IsResident"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("true")]
		public bool IsAutoRefreshTimeline{
			get{
				return (bool)this["IsAutoRefreshTimeline"];
			}
			set{
				this["IsAutoRefreshTimeline"] = value;
			}
		}
		/*
		[UserScopedSetting]
		[DefaultSettingValue("60")]
		public int AutoRefreshTimelineInterval{
			get{
				return (int)this["AutoRefreshTimelineInterval"];
			}
			set{
				if(value < 60){
					throw new ArgumentOutOfRangeException();
				}
				this["AutoRefreshTimelineInterval"] = value;
			}
		}
		*/
		[UserScopedSetting]
		[DefaultSettingValue("true")]
		public bool IsUseUserColor{
			get{
				return (bool)this["IsUseUserColor"];
			}
			set{
				this["IsUseUserColor"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("20")]
		public int TimelineCount{
			get{
				return (int)this["TimelineCount"];
			}
			set{
				if((value < 1) && (value > 200)){
					throw new ArgumentOutOfRangeException();
				}
				this["TimelineCount"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("")]
		public DateTime LatestTimelineStatusDateTime{
			get{
				return (DateTime)this["LatestTimelineStatusDateTime"];
			}
			set{
				this["LatestTimelineStatusDateTime"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("64")]
		public int OutputItemLimitCount{
			get{
				return (int)this["OutputItemLimitCount"];
			}
			set{
				if(value <= 0){
					throw new ArgumentOutOfRangeException();
				}
				this["OutputItemLimitCount"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("64")]
		public GridLength OutputListRowHeight{
			get{
				return (GridLength)this["OutputListRowHeight"];
			}
			set{
				this["OutputListRowHeight"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("auto")]
		public GridLength TimelineStatusEditRowHeight{
			get{
				return (GridLength)this["TimelineStatusEditRowHeight"];
			}
			set{
				this["TimelineStatusEditRowHeight"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("false")]
		public bool IsUseWebProxy{
			get{
				return (bool)this["IsUseWebProxy"];
			}
			set{
				this["IsUseWebProxy"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("false")]
		public bool IsUseProfileBackground{
			get{
				return (bool)this["IsUseProfileBackground"];
			}
			set{
				this["IsUseProfileBackground"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("")]
		[SettingsSerializeAs(SettingsSerializeAs.Binary)]
		public Key ShowWindowHotKey{
			get{
				return (Key)this["ShowWindowHotKey"];
			}
			set{
				this["ShowWindowHotKey"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("")]
		[SettingsSerializeAs(SettingsSerializeAs.Binary)]
		public ModifierKeys ShowWindowHotKeyModifiers{
			get{
				return (ModifierKeys)this["ShowWindowHotKeyModifiers"];
			}
			set{
				this["ShowWindowHotKeyModifiers"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("")]
		public Guid ThemeGuid{
			get{
				return (Guid)this["ThemeGuid"];
			}
			set{
				this["ThemeGuid"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("false")]
		public bool IsExpandUrlsInStatus{
			get{
				return (bool)this["IsExpandUrlsInStatus"];
			}
			set{
				this["IsExpandUrlsInStatus"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("true")]
		public bool IsUseProfileImageAsMainFormIcon{
			get{
				return (bool)this["IsUseProfileImageAsMainFormIcon"];
			}
			set{
				this["IsUseProfileImageAsMainFormIcon"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("")]
		public string[] Hashes{
			get{
				return (string[])this["Hashes"];
			}
			set{
				this["Hashes"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("true")]
		public bool IsUseAutoComplete{
			get{
				return (bool)this["IsUseAutoComplete"];
			}
			set{
				this["IsUseAutoComplete"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("")]
		public string AutoCompleteAddtionalDictionaryFile{
			get{
				return (string)this["AutoCompleteAddtionalDictionaryFile"];
			}
			set{
				this["AutoCompleteAddtionalDictionaryFile"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("[^a-zA-Z@#_]")]
		public string AutoCompleteTokenPattern{
			get{
				return (string)this["AutoCompleteTokenPattern"];
			}
			set{
				this["AutoCompleteTokenPattern"] = value;
			}
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("true")]
		public bool IsExpandImagesInStatus{
			get{
				return (bool)this["IsExpandImagesInStatus"];
			}
			set{
				this["IsExpandImagesInStatus"] = value;
			}
		}
		
		#endregion
		
		#region Obsolete
		
		[UserScopedSetting]
		[DefaultSettingValue(null)]
		[Obsolete]
		public Uri WebProxyUri{
			get{
				return (Uri)this["WebProxyUri"];
			}
			set{
				this["WebProxyUri"] = value;
			}
		}
		
		#endregion
	}
	
	/// <summary>
	/// ウインドウの設定クラス
	/// </summary>
	public class WindowSettings : UpgradeOnceApplicationSettingsBase{
		public WindowSettings(){}
		public WindowSettings(string key) : base(key){}
		
		[UserScopedSetting]
		[DefaultSettingValue("NaN, NaN, 640, 480")]
		public Rect RestoreBounds{
			get{
				return VerifyRect((Rect)this["RestoreBounds"]);
			}
			set{
				this["RestoreBounds"] = value;
			}
		}
		
		private Rect VerifyRect(Rect rect){
			Double width = rect.Width;
			Double height = rect.Height;
			Double x = rect.X;
			Double y = rect.Y;
			if(!ValidSizeValue(rect.Width)){
				width = 640;
			}
			if(!ValidSizeValue(rect.Height)){
				height = 480;
			}
			if(!ValidPositionValue(rect.X)){
				x = Double.NaN;
			}
			if(!ValidPositionValue(rect.Y)){
				y = Double.NaN;
			}
			return new Rect(x, y, width, height);
		}
		
		[UserScopedSetting]
		[DefaultSettingValue("Normal")]
		public WindowState WindowState{
			get{
				return (WindowState)this["WindowState"];
			}
			set{
				if(value == WindowState.Minimized){
					throw new ArgumentException();
				}
				this["WindowState"] = value;
			}
		}
		
		private static bool ValidSizeValue(double v){
			return ValidPositionValue(v);
		}
		
		private static bool ValidPositionValue(double v){
			return ((Double.NegativeInfinity < v) && (v < Double.PositiveInfinity));
		}
	}
}