using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Xml.Serialization;
using System.Reflection;
using System.Diagnostics;

namespace Test
{
    using WebServiceClient;
    using WebServiceClient.Net;
    using WebServiceClient.Collections;
    using WebServiceClient.NicoVideo;

    public partial class frmTest : Form
    {
        #region Field

        private NicoApiClient _nicoApi;
        private PropertyInfo _propertyInfo;
        private int _maxDownload;
        private string _videoTitle;
        private string _configPath;
        private DateTime _oldComment;
        private int _formHeight;

        #endregion

        #region Constructor

        public frmTest()
        {
            InitializeComponent();

            _nicoApi = NicoApiClient.Instance;
            _nicoApi.Invoker = this;
            _nicoApi.MaxExecution = 1;
            _nicoApi.SetTaskExecution(NicoRequestType.DownloadVideo, 1);
            _nicoApi.SetTaskWaitTimeAll(1000);
            _nicoApi.SetTaskWaitTime(NicoRequestType.DownloadVideo, 3000);
            _nicoApi.SetRequestWaitTimeAll(250);
            _nicoApi.Interval = 100;

            _nicoApi.TaskException += _nico_TaskExecution;
            _nicoApi.RequestTrace += _nico_RequestTrace;
            _nicoApi.LoginStateCompleted += _nico_LoginStateCompleted;
            _nicoApi.LoginCompleted += _nico_LoginCompleted;
            _nicoApi.AllVideoInfoCompleted += _nico_AllVideoInfoCompleted;
            _nicoApi.GetCommentProgress += _nico_GetCommentProgress;
            _nicoApi.GetCommentCompleted += _nico_GetCommentCompleted;
            _nicoApi.DownloadVideoBegin += _nico_DownloadVideoBegin;
            _nicoApi.DownloadVideoProgress += _nico_DownloadVideoProgress;
            _nicoApi.DownloadVideoCompleted += _nico_DownloadVideoCompleted;

            _propertyInfo = new PropertyInfo();

            Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
            _configPath = Path.GetDirectoryName(assembly.Location) + "\\" + Path.GetFileNameWithoutExtension(assembly.Location) + ".dat";

            FileVersionInfo version = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location);
            this.Text += version.ProductVersion;

            ApplicationSettings settings = null;
            try
            {
                settings = SerializeHelper.XmlDeserialize<ApplicationSettings>(_configPath, Encoding.Unicode, true);
            }
            catch
            {
                settings = new ApplicationSettings();
            }
            finally
            {
                this.Left = settings.PositonX;
                this.Top = settings.PositonY;
                txtEmail.Text = settings.Email;
                txtFolder.Text = settings.Folder;
                txtVideoId.Text = settings.VideoId;
                numComment.Value = settings.Comments;
                dateComment.Value = DateTime.Now;
            }

            chkInfo.Checked = false;
        }

        #endregion

        #region Form Events

        private void frmTest_FormClosed(object sender, FormClosedEventArgs e)
        {
            try
            {
                ApplicationSettings settings = new ApplicationSettings();
                settings.PositonX = this.Left;
                settings.PositonY = this.Top;
                settings.Email = txtEmail.Text;
                settings.Folder = txtFolder.Text;
                settings.VideoId = txtVideoId.Text;
                settings.Comments = numComment.Value;
                SerializeHelper.XmlSerialize(settings, _configPath, Encoding.Unicode, true);
                _nicoApi.Dispose();
            }
            finally
            {
            }
        }

        private void btnGetLoginState_Click(object sender, EventArgs e)
        {
            groupLogin.Enabled = false;
            _nicoApi.LoginStateAsync(null);
        }

        private void btnLogin_Click(object sender, EventArgs e)
        {
            groupLogin.Enabled = false;
            _nicoApi.LoginAsync(txtEmail.Text, txtPassword.Text, null);
        }

        private void btnFolder_Click(object sender, EventArgs e)
        {
            FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog();
            DialogResult dialogResult = folderBrowserDialog.ShowDialog();
            if (dialogResult == DialogResult.OK)
            {
                txtFolder.Text = folderBrowserDialog.SelectedPath;
                btnDownload.Enabled = (txtFolder.Text.Length > 0 && txtVideoId.Text.Length > 0);
            }
        }

        private void txtVideoId_TextChanged(object sender, EventArgs e)
        {
            btnDownload.Enabled = (txtFolder.Text.Length > 0 && txtVideoId.Text.Length > 0);
            btnComment.Enabled = false;
        }

        private void chkCommentOnly_CheckedChanged(object sender, EventArgs e)
        {
            dateComment.Enabled = chkCommentOnly.Checked;
        }

        private void btnDownload_Click(object sender, EventArgs e)
        {
            groupLogin.Enabled = false;
            panelDownload.Enabled = false;
            btnDownload.Enabled = false;
            btnComment.Enabled = false;
            btnCancel.Enabled = true;
            lblTitle.Text = "動画ID：" + txtVideoId.Text;
            _oldComment = DateTime.MinValue;

            if (chkCommentOnly.Checked == false)
            {
                string dir = txtFolder.Text + "\\<TITLE>(<ID>).<EXT>";
                _maxDownload = progressBar.Maximum - (int)(numComment.Value / 5);
                progressBar.Value = 0;
                _nicoApi.DownloadVideoAsync(txtVideoId.Text, dir, NicoThreadType.None, true, true, null);
            }
            else
            {
                _maxDownload = progressBar.Maximum - (int)(numComment.Value / 5);
                progressBar.Value = _maxDownload;
                _nicoApi.AllVideoInfoAsync(txtVideoId.Text, null);
            }
        }

        private void btnComment_Click(object sender, EventArgs e)
        {
            groupLogin.Enabled = false;
            panelDownload.Enabled = false;
            btnDownload.Enabled = false;
            btnComment.Enabled = false;
            btnCancel.Enabled = true;

            _maxDownload = progressBar.Maximum - (int)(numComment.Value / 5);
            progressBar.Value = _maxDownload;
            _nicoApi.GetCommentAsync(txtVideoId.Text, _oldComment, (int)-numComment.Value, NicoThreadType.Default, null);
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            groupLogin.Enabled = true;
            panelDownload.Enabled = true;
            btnDownload.Enabled = true;
            btnCancel.Enabled = false;
            _nicoApi.CancelAll();
        }

        private void chkInfo_CheckedChanged(object sender, EventArgs e)
        {
            if (chkInfo.Checked == false)
            {
                int height = chkInfo.Top + chkInfo.Height + (this.Height - this.ClientSize.Height + 4);
                chkInfo.Text = "＋ デバッグ情報";
                _formHeight = this.Height;
                this.MaximumSize = new Size(800, height);
                this.MinimumSize = new Size(398, height);
                this.Height = height;
                propInfo.Visible = false;
                panelTabs.Visible = false;
            }
            else
            {
                chkInfo.Text = "− デバッグ情報";
                this.MaximumSize = new Size(1024, 888);
                this.MinimumSize = new Size(398, 438);
                this.Height = _formHeight;
                propInfo.Visible = true;
                panelTabs.Visible = true;
            }
        }

        private void radioProp_CheckedChanged(object sender, EventArgs e)
        {
            if (radioInfo.Checked == true)
            {
                panelInfo.Visible = true;
                panelLog.Visible = false;
            }
            else
            {
                panelInfo.Visible = false;
                panelLog.Visible = true;
            }
        }

        #endregion

        #region NicoServiceClient Events

        /// <summary>タスク実行中もしくはイベントハンドラで例外が発生した場合、このメソッドに通知されます。</summary>
        /// <param name="sender">例外の発生したリクエストを表す AsyncWebRequest。</param>
        /// <param name="args">タスクの例外通知を受けるための AsyncWebExceptionEventArgs。</param>
        private void _nico_TaskExecution(AsyncWebRequest sender, AsyncWebExceptionEventArgs args)
        {
            MessageBox.Show(this, args.Error.Message, args.Error.GetType().ToString(), MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
        }

        private void _nico_RequestTrace(AsyncWebRequest sender, RequestTraceEventArgs e)
        {
            string message;
            if (e.Response == null)
            {
                message = string.Format("({0}) {1}:{2}\r\n", e.Request.TaskId, ((NicoRequestType)e.Request.TypeId).ToString(), e.State.ToString());
            }
            else
            {
                message = string.Format("({0}) {1}:{2}, {3}\r\n", e.Request.TaskId, ((NicoRequestType)e.Request.TypeId).ToString(), e.State.ToString(), e.Response.ResponseUri);
            }

            textLog.AppendText(message);

            if (e.State == TraceState.End && e.Request.IsChildRequest == false)
            {
                textLog.AppendText(string.Format("実行中のスレッド数:{0}\r\n\r\n", _nicoApi.Requests));
            }
        }

        private void _nico_LoginStateCompleted(AsyncHttpRequest sender, LoginStateCompletedEventArgs e)
        {
            groupLogin.Enabled = true;

            if (sender.Cancelled == true)
            {
                MessageBox.Show("キャンセルされました", "ブラウザから取得");
            }
            else if (sender.Error != null)
            {
                MessageBox.Show(sender.Error.Message);
            }
            else if (e.Contents.LoggedIn == true)
            {
                groupDownload.Enabled = true;
                btnDownload.Enabled = (txtFolder.Text.Length > 0 && txtVideoId.Text.Length > 0);
            }
            else
            {
                MessageBox.Show("ログインしていません", "ブラウザから取得");
            }
        }

        private void _nico_LoginCompleted(AsyncHttpRequest sender, LoginCompletedEventArgs e)
        {
            groupLogin.Enabled = true;
            groupDownload.Enabled = _nicoApi.LoggedIn;

            if (sender.Cancelled == true)
            {
                MessageBox.Show("キャンセルされました", "ログイン");
            }
            else if (sender.Error != null)
            {
                MessageBox.Show(sender.Error.Message);
            }
            else if (e.Contents.LoggedIn == true)
            {
                groupDownload.Enabled = true;
                btnDownload.Enabled = (txtFolder.Text.Length > 0 && txtVideoId.Text.Length > 0);
            }
            else
            {
                MessageBox.Show("ログイン出来ませんでした", "ログイン");
            }
        }

        private void _nico_DownloadVideoBegin(AsyncHttpRequest sender, DownloadVideoBeginEventArgs e)
        {
            lblTitle.Text = e.Contents.VideoInfo.Title;

            // ファイルが存在するかどうか確認する
            if (File.Exists(e.Contents.FullPath) == true)
            {
                switch (MessageBox.Show(e.Contents.FileName + " はすでに存在します。上書きしますか？", "確認", MessageBoxButtons.OKCancel))
                {
                case DialogResult.OK:
                    // 上書きするなら何もしない
                    break;
                case DialogResult.Cancel:
                    // キャンセルされたら中止する
                    sender.Cancel();
                    break;
                }
            }
        }

        private void _nico_DownloadVideoProgress(AsyncHttpRequest sender, DownloadVideoProgressEventArgs e)
        {
            progressBar.Value = (int)((double)e.Contents.Download * _maxDownload / e.Contents.Size);
            _propertyInfo.SetDownloadVideoInfo(e.Contents);
            propInfo.SelectedObject = _propertyInfo;
        }

        private void _nico_DownloadVideoCompleted(AsyncHttpRequest sender, DownloadVideoCompletedEventArgs e)
        {
            groupDownload.Enabled = _nicoApi.LoggedIn;

            if (sender.Cancelled == true || (sender.Error == null && numComment.Value == 0))
            {
                groupLogin.Enabled = true;
                panelDownload.Enabled = true;
                btnDownload.Enabled = true;
                btnComment.Enabled = false;
                btnCancel.Enabled = false;
                progressBar.Value = 0;

                if (sender.Cancelled == true)
                {
                    System.Media.SystemSounds.Beep.Play();
                }
            }
            else
            {
                if (sender.Error != null)
                {
                    MessageBox.Show(sender.Error.Message);
                    groupLogin.Enabled = true;
                    panelDownload.Enabled = true;
                    btnDownload.Enabled = true;
                    btnComment.Enabled = false;
                    btnCancel.Enabled = false;
                    progressBar.Value = 0;
                }
                else
                {
                    _propertyInfo.SetDownloadVideoInfo(e.Contents);
                    propInfo.SelectedObject = _propertyInfo;
                    _videoTitle = e.Contents.FileName;
                    _nicoApi.GetCommentAsync(txtVideoId.Text, DateTime.MinValue, (int)-numComment.Value, NicoThreadType.Default, null);
                }
            }
        }

        private void _nico_AllVideoInfoCompleted(AsyncHttpRequest sender, AllVideoInfoCompletedEventArgs e)
        {
            groupDownload.Enabled = _nicoApi.LoggedIn;

            if (sender.Cancelled == true || sender.Error != null)
            {
                groupLogin.Enabled = true;
                panelDownload.Enabled = true;
                btnDownload.Enabled = true;
                btnCancel.Enabled = false;
            }

            if (sender.Cancelled == false)
            {
                if (sender.Error != null)
                {
                    MessageBox.Show(sender.Error.Message);
                    progressBar.Value = 0;
                }
                else
                {
                    _propertyInfo.SetAllVideoInfo(e.Contents);
                    propInfo.SelectedObject = _propertyInfo;
                    _videoTitle = e.Contents.Title + "(" + txtVideoId.Text + ")";
                    _nicoApi.GetCommentAsync(txtVideoId.Text, dateComment.Value, (int)-numComment.Value, NicoThreadType.Default, null);
                }
            }
        }

        private void _nico_GetCommentProgress(AsyncHttpRequest sender, GetCommentProgressEventArgs e)
        {
            if (progressBar.IsHandleCreated == true)
            {
                progressBar.Value = _maxDownload + e.Contents.Thread.CommentCount / 5;
            }
        }

        void _nico_GetCommentCompleted(AsyncHttpRequest sender, GetCommentCompletedEventArgs e)
        {
            groupDownload.Enabled = _nicoApi.LoggedIn;
            groupLogin.Enabled = true;
            panelDownload.Enabled = true;
            btnDownload.Enabled = true;
            btnCancel.Enabled = false;

            if (sender.Cancelled == false)
            {
                if (sender.Error != null)
                {
                    MessageBox.Show(sender.Error.Message);
                    progressBar.Value = 0;
                    _oldComment = DateTime.MinValue;
                }
                else
                {
                    string commentPath = txtFolder.Text + "\\" + FileIOHelper.ReplaceInvalidFileNameChars(_videoTitle) + ".xml";
                    NicoThread thread = e.Contents.Thread;

                    if (File.Exists(commentPath) == true)
                    {
                        thread.Merge(new NicoThread(commentPath, new UTF8Encoding(false)));
                    }

                    // BoonSutazio に依存するコメントはヘッダに指定すればよい
                    string header = string.Format("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- BoonSutazioData={0} -->\n", e.Contents.VideoId);
                    string footer = "";

                    // コメントを番号順で並べ替える
                    thread.SortComment(delegate(IKeyValuePair<int, NicoCommentEntry> x, IKeyValuePair<int, NicoCommentEntry> y)
                    {
                        return (int)(x.Value.No - y.Value.No);
                    });

                    // ボタンを番号順で並べ替える
                    thread.SortButton(delegate(IKeyValuePair<int, NicoButtonEntry> x, IKeyValuePair<int, NicoButtonEntry> y)
                    {
                        return (int)(x.Value.No - y.Value.No);
                    });

                    // XML ファイルに書き出す
                    // ※GetThreadXml でもXMLソースを得られるがスタックを消費するのでオススメ出来ない
                    thread.SaveThreadXml(NicoThreadType.Default, commentPath, new UTF8Encoding(false), header, footer);

                    // 次回取得時の基準日を保存する
                    _oldComment = thread.GetFirstDate();

                    System.Media.SystemSounds.Beep.Play();
                    progressBar.Value = progressBar.Maximum;
                }
            }

            btnComment.Enabled = _oldComment != DateTime.MinValue;
        }

        #endregion

        #region ApplicationSettings

        /// <summary>アプリケーションの設定情報を提供します。</summary>
        [Serializable()]
        [XmlRoot("ApplicationSettings")]
        public sealed class ApplicationSettings
        {
            #region Field

            /// <summary>ウィンドウの水平位置を表す int。</summary>
            private int _positonX;
            /// <summary>ウィンドウの垂直位置を表す int。</summary>
            private int _positonY;
            /// <summary>メールアドレスを表す string。</summary>
            private string _email;
            /// <summary>ダウンロード先のフォルダを表す string。</summary>
            private string _folder;
            /// <summary>ダウンロードする動画IDを表す string。</summary>
            private string _videoId;
            /// <summary>ダウンロードするコメント数を表す decimal。</summary>
            private decimal _comments;

            #endregion

            #region Constructor

            /// <summary>オブジェクトを初期化します。</summary>
            public ApplicationSettings()
            {
                _positonX = Screen.PrimaryScreen.WorkingArea.Width / 2 - 195;
                _positonY = Screen.PrimaryScreen.WorkingArea.Height / 2 - 85;
                _folder = "";
                _videoId = "";
                _comments = 1000;
            }

            #endregion

            #region Property

            /// <summary>ウィンドウの水平位置を取得・設定します。</summary>
            [XmlElement("positon_x", typeof(int), IsNullable = false)]
            public int PositonX
            {
                get { return _positonX; }
                set { _positonX = value; }
            }

            /// <summary>ウィンドウの垂直位置を取得・設定します。</summary>
            [XmlElement("positon_y", typeof(int), IsNullable = false)]
            public int PositonY
            {
                get { return _positonY; }
                set { _positonY = value; }
            }

            /// <summary>メールアドレスを取得・設定します。</summary>
            [XmlElement("email", typeof(string), IsNullable = true)]
            public string Email
            {
                get { return _email; }
                set { _email = value; }
            }
            
            /// <summary>ダウンロード先のフォルダを取得・設定します。</summary>
            [XmlElement("folder", typeof(string), IsNullable = true)]
            public string Folder
            {
                get { return _folder; }
                set { _folder = value; }
            }

            /// <summary>ダウンロードする動画IDを取得・設定します。</summary>
            [XmlElement("video_id", typeof(string), IsNullable = true)]
            public string VideoId
            {
                get { return _videoId; }
                set { _videoId = value; }
            }

            /// <summary>ダウンロードするコメント数を取得・設定します。</summary>
            [XmlElement("comments", typeof(decimal), IsNullable = false)]
            public decimal Comments
            {
                get { return _comments; }
                set { _comments = value; }
            }

            #endregion
        }

        #endregion

        #region PropertyInfo

        private class PropertyInfo
        {
            #region Field

            /// <summary>動画にアクセスした直近の時刻を表す DateTime。</summary>
            protected DateTime _accessTime;
            /// <summary>削除された原因を表す NicoDeletedStatus。</summary>
            protected NicoDeletedStatus _deleted;
            /// <summary>動画のタイトルを表す string。</summary>
            protected string _title;
            /// <summary>動画の説明文を表す string。</summary>
            protected string _description;
            /// <summary>動画のタイプを表す string。</summary>
            protected string _type;
            /// <summary>再生時間を表す TimeSpan。</summary>
            protected TimeSpan _length;
            /// <summary>動画の投稿日時を表す DateTime。</summary>
            protected DateTime _firstRetrieve;
            /// <summary>直近のコメントを表す string。</summary>
            protected string _lastResBody;
            /// <summary>プレミアムアカウントかどうかを表す bool。</summary>
            protected bool _isPremium;
            /// <summary>エコノミーモードかどうかを表す bool。</summary>
            protected bool _isEconomy;
            /// <summary>再生回数を表す long。</summary>
            protected long _view;
            /// <summary>コメント数を表す long。</summary>
            protected long _comment;
            /// <summary>マイリスト登録数を表す long。</summary>
            protected long _mylist;
            /// <summary>コンテンツのサイズを表す long。</summary>
            protected long _size;
            /// <summary>ダウンロード済みのファイルサイズを表す long。</summary>
            protected long _download;

            #endregion

            #region Constructor

            public PropertyInfo()
            {
            }

            #endregion

            #region Property

            /// <summary>動画にアクセスした直近の時刻を取得します(読み取り専用)。</summary>
            public DateTime AccessTime
            {
                get { return _accessTime; }
            }

            /// <summary>削除された原因を取得します(読み取り専用)。</summary>
            public NicoDeletedStatus Deleted
            {
                get { return _deleted; }
            }

            /// <summary>動画のタイトルを取得します(読み取り専用)。</summary>
            public string Title
            {
                get { return _title; }
            }

            /// <summary>動画の説明文を取得します(読み取り専用)。</summary>
            public string Description
            {
                get { return _description; }
            }

            /// <summary>動画のタイプを取得します(読み取り専用)。</summary>
            public string Type
            {
                get { return _type; }
            }

            /// <summary>再生時間を取得します(読み取り専用)。</summary>
            public TimeSpan Length
            {
                get { return _length; }
            }

            /// <summary>動画の投稿日時を取得します(読み取り専用)。</summary>
            public DateTime FirstRetrieve
            {
                get { return _firstRetrieve; }
            }

            /// <summary>直近のコメントを取得します(読み取り専用)。</summary>
            public string LastResBody
            {
                get { return _lastResBody; }
            }

            /// <summary>プレミアムアカウントかどうかを取得します(読み取り専用)。</summary>
            public bool IsPremium
            {
                get { return _isPremium; }
            }

            /// <summary>エコノミーモードかどうかを取得します(読み取り専用)。</summary>
            public bool IsEconomy
            {
                get { return _isEconomy; }
            }

            /// <summary>再生回数を取得します(読み取り専用)。</summary>
            public long View
            {
                get { return _view; }
            }

            /// <summary>コメント数を取得します(読み取り専用)。</summary>
            public long Comment
            {
                get { return _comment; }
            }

            /// <summary>マイリスト登録数を取得します(読み取り専用)。</summary>
            public long Mylist
            {
                get { return _mylist; }
            }

            /// <summary>コンテンツのサイズを取得・設定します。</summary>
            public long Size
            {
                get { return _size; }
            }

            /// <summary>ダウンロード済みのファイルサイズを取得・設定します。</summary>
            public long Download
            {
                get { return _download; }
            }

            #endregion

            #region Method

            public void SetDownloadVideoInfo(DownloadVideoContents contents)
            {
                _accessTime = contents.VideoInfo.AccessTime;
                _deleted = contents.VideoInfo.Deleted;
                _title = contents.VideoInfo.Title;
                _description = contents.VideoInfo.Description;
                _type = contents.VideoInfo.Type;
                _length = contents.VideoInfo.Length;
                _firstRetrieve = contents.VideoInfo.FirstRetrieve;
                _lastResBody = contents.VideoInfo.LastResBody;
                _isPremium = contents.VideoInfo.IsPremium;
                _isEconomy = contents.VideoInfo.IsEconomy;
                _view = contents.VideoInfo.ViewCount;
                _comment = contents.VideoInfo.CommentCount;
                _mylist = contents.VideoInfo.MylistCount;
                _size = contents.Size;
                _download = contents.Download;
            }

            public void SetAllVideoInfo(AllVideoInfoContents contents)
            {
                _accessTime = contents.AccessTime;
                _deleted = contents.Deleted;
                _title = contents.Title;
                _description = contents.Description;
                _type = contents.Type;
                _length = contents.Length;
                _firstRetrieve = contents.FirstRetrieve;
                _lastResBody = contents.LastResBody;
                _isPremium = contents.IsPremium;
                _isEconomy = contents.IsEconomy;
                _view = contents.ViewCount;
                _comment = contents.CommentCount;
                _mylist = contents.MylistCount;
                _size = 0;
                _download = 0;
            }

            #endregion
        }

        #endregion
    }
}