//ライセンス情報はAgeOnフォルダのLISENCEフォルダを参照してください
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Net;
using System.IO;
using System.Threading;
using System.IO.Compression;

namespace nispi
{
    class DownloadDat
    {
        public string httpResData;

        //public MemoryStream httpResRawData;
        public int httpResRawDataLength;

        private ManualResetEvent allDone;
        private int type;
        private string datUrl;

        public string command = "";
        //private bool Kako = false;

        private string dir;
        private string datFileName;

        const int BUFFER_SIZE = 4096;

        public DownloadDat()
        {
            httpResData = "";
        }

        public DownloadDat(bool flag)
        {
            httpResData = "";
            //Kako = true;
        }

        public void DownloadDatData(ref string responseStatusCode, ref string command, string accessUrl)
        {
            this.type = Util.BoardType(accessUrl);
            this.datUrl = accessUrl;

            allDone = new ManualResetEvent(false);

            Uri u = new Uri(accessUrl);

            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(u);
            req.KeepAlive = false;
            req.ProtocolVersion = HttpVersion.Version11;
            req.Method = "GET";

            //ユーザーエージェント
            if (Const.opt.otherOption.UserAgent.Length > 0)
                req.UserAgent = Const.opt.otherOption.UserAgent;
            else
                req.UserAgent = Const.USER_AGENT;

            req.AutomaticDecompression = DecompressionMethods.GZip;
            req.Timeout = 30000;

            //プロクシ
            if ((Const.opt.otherOption.IsProxyUse.Equals("True") == true) &&
                (Const.opt.otherOption.ProxyAddress.Equals("none") == false) &&
                (Const.opt.otherOption.ProxyAddress.Length > 0))
            {
                WebProxy proxy = new WebProxy(
                    "http://" + Const.opt.otherOption.ProxyAddress + ":" +
                    Const.opt.otherOption.ProxyPort);
                req.Proxy = proxy;
            }
            else
                req.Proxy = null;

            RequestState myRequestState = new RequestState();
            myRequestState.request = req;


            // Start the asynchronous request.
            IAsyncResult result =
              (IAsyncResult)req.BeginGetResponse(new AsyncCallback(RespCallback), myRequestState);

            allDone.WaitOne();

            //次の動作を取得
            command = myRequestState.command;
            //エラーの数字を取得
            responseStatusCode = myRequestState.responseStatusCode;

            if (myRequestState.responseStatusCode.StartsWith("20") == true)
            {
                ;
            }
            else
            {
                try
                {
                    myRequestState.response.Close();
                }
                catch { ;}
                return;
            }

            //--受信したデータを見る
            if ((httpResData == null) || (httpResData.Length < 1))
            {
                myRequestState.responseStatusCode = "996";
                myRequestState.command = "終了";
                try
                {
                    myRequestState.response.Close();
                }
                catch { ;}
                return;
            }

            string res_etag = myRequestState.response.GetResponseHeader("ETag");
            string res_last_modified = myRequestState.response.GetResponseHeader("Last-Modified");

            string dir = Util.DatUrlToDatDirPath(this.datUrl);
            string dat_file_name = Util.DatUrlToDatFileName(this.datUrl);

            Downloader.save(dir, dat_file_name, httpResData);

            if ((res_etag != null) && (res_last_modified != null) && (res_etag.Length > 0) && (res_last_modified.Length > 0))
                Downloader.save_infomation(dir, dat_file_name +
                    ".info", res_etag, res_last_modified, this.httpResRawDataLength.ToString());

            myRequestState.response.Close();
            return;
        }

        //２回目以降。差分を得るとき
        public void DownloadDatData2(string oldData, ref string responseStatusCode, ref string command, string url)
        {
            this.type = Util.BoardType(url);
            this.datUrl = url;

            allDone = new ManualResetEvent(false);

            Uri u = new Uri(url);

            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(u);
            req.KeepAlive = false;
            req.ProtocolVersion = HttpVersion.Version10;
            req.Method = "GET";
            //ユーザーエージェント
            if (Const.opt.otherOption.UserAgent.Length > 0)
                req.UserAgent = Const.opt.otherOption.UserAgent;
            else
                req.UserAgent = Const.USER_AGENT;
            //req.AutomaticDecompression = DecompressionMethods.GZip;
            req.Timeout = 10000;

            //プロクシ
            if ((Const.opt.otherOption.IsProxyUse.Equals("True") == true) &&
                (Const.opt.otherOption.ProxyAddress.Equals("none") == false) &&
                (Const.opt.otherOption.ProxyAddress.Length > 0))
            {
                WebProxy proxy = new WebProxy(
                    "http://" + Const.opt.otherOption.ProxyAddress + ":" +
                    Const.opt.otherOption.ProxyPort);
                req.Proxy = proxy;
            }
            else
                req.Proxy = null;

            //スレッドの情報を得る
            dir = Util.DatUrlToDatDirPath(this.datUrl);
            datFileName = Util.DatUrlToDatFileName(this.datUrl);

            string old_size = Downloader.get_info(dir, datFileName + ".info", "size");
            string etag = Downloader.get_info(dir, datFileName + ".info", "etag");
            string last_modified = Downloader.get_info(dir, datFileName + ".info", "last_modified");

            if ((old_size == null) || (old_size.Length == 0) ||
                (etag == null) || (etag.Length == 0) ||
                (last_modified == null) || (last_modified.Length == 0))
            {
                command = "全取得";
                return;
            }

            req.AddRange(int.Parse(old_size));
            System.DateTime datetemp;
            if (System.DateTime.TryParse(last_modified, out datetemp) == true)
                req.IfModifiedSince = datetemp;
            req.Headers.Set("If-None-Match", etag);
            //------//

            RequestState myRequestState = new RequestState();
            myRequestState.request = req;

            IAsyncResult result =
              (IAsyncResult)req.BeginGetResponse(new AsyncCallback(RespCallback), myRequestState);

            allDone.WaitOne();

            //次の動作を取得
            command = myRequestState.command;
            //エラーの数字を取得
            responseStatusCode = myRequestState.responseStatusCode;

            if (myRequestState.responseStatusCode.StartsWith("20") == true)
            {
                this.httpResData = oldData + this.httpResData;
            }
            else
            {
                try
                {
                    myRequestState.response.Close();
                }
                catch { ;}
                this.httpResData = oldData;
                return;
            }

            //---受信したデータを調べる
            if ((httpResData == null) || (httpResData.Length < 1))
            {
                myRequestState.responseStatusCode = "995";
                myRequestState.command = "終了";
                try
                {
                    myRequestState.response.Close();
                }
                catch { ;}
                return;
            }

            string res_etag = myRequestState.response.GetResponseHeader("ETag");
            string res_last_modified = myRequestState.response.GetResponseHeader("Last-Modified");
            int size = this.httpResRawDataLength + Int32.Parse(old_size);

            Downloader.save(dir, datFileName, httpResData);
            //Downloader.save_add(dir, dat_file_name, this.httpResData);
            if ((res_etag != null) && (res_last_modified != null) && (res_etag.Length > 0) && (res_last_modified.Length > 0))
                Downloader.save_infomation(dir, datFileName + ".info", res_etag, res_last_modified, size.ToString());

            myRequestState.response.Close();
            return;
        }

        private void RespCallback(IAsyncResult asynchronousResult)
        {
            try
            {
                RequestState myRequestState = (RequestState)asynchronousResult.AsyncState;
                HttpWebRequest myHttpWebRequest = myRequestState.request;


                try
                {
                    myRequestState.response = (HttpWebResponse)myHttpWebRequest.EndGetResponse(asynchronousResult);

                    //↓エラーでもなくＯＫとしてこのアドレスに転送される。（ヒット例：過去ログ倉庫入りのＤＡＴにアクセス、）
                    if (myRequestState.response.ResponseUri.AbsoluteUri.Equals("http://www2.2ch.net/nogood.html") == true)
                    {
                        myRequestState.responseStatusCode = "996";
                        myRequestState.command = "終了";
                        allDone.Set();
                        return;
                    }
                    //過去ログにアクセスした場合の処理（例外的処理）
                    if (true == myHttpWebRequest.Address.AbsoluteUri.Equals("http://www2.2ch.net/live.html"))
                    {
                        myRequestState.responseStatusCode = "997";
                        myRequestState.command = "過去ログ０";
                        allDone.Set();
                        return;
                    }

                    if (myRequestState.response.StatusCode == HttpStatusCode.OK)
                    {
                        myRequestState.responseStatusCode = "200";
                        myRequestState.command = "終了";
                    }
                    else if (myRequestState.response.StatusCode == HttpStatusCode.PartialContent)
                    {
                        myRequestState.responseStatusCode = "206";
                        myRequestState.command = "終了";
                    }
                }
                catch (System.Net.WebException e)
                {
                    //リクエストのＤＡＴとabsoluteuriのＤＡＴのアドレスが共通ならば通常処理。
                    //それ以外に飛ばされていればエラーとして読み込みは破棄する。
                    //飛ばし先に変更なし(ほとんど起こらない)

                    if (e.Response.ResponseUri.AbsoluteUri.Equals("http://www2.2ch.net/nogood.html") == true)
                    {
                        //今後のあらゆるエラーに備える。このアドレスならとりあえず終了
                        myRequestState.responseStatusCode = "996";
                        myRequestState.command = "終了";
                        allDone.Set();
                        return;
                    }

                    if (e.Message.Contains("304") == true)
                    {
                        //変更が無い
                        myRequestState.responseStatusCode = "304";
                        myRequestState.command = "終了";
                        allDone.Set();
                        return;
                    }

                    else if (e.Message.Contains("302") == true)
                    {
                        //見つからない

                        //Locationを見てボボン規制をチェックする。
                        /*
                         * ステータスが302であっても、応答ヘッダのLocationに
                         * http://*.2ch.net/_403/もしくはhttp://www2.2ch.net/403/があれば
                         * バーボン規制・ボボン規制として処理して下さい。
                         * http://www.monazilla.org/index.php?e=198
                         */
                        if (((e.Response.Headers[HttpResponseHeader.Location].Contains("2ch.net/_403/") == true) ||
                            (e.Response.Headers[HttpResponseHeader.Location].Contains("2ch.net/403/") == true)))
                        {
                            myRequestState.responseStatusCode = "403";
                            myRequestState.command = "終了";
                        }
                        else
                        {
                            myRequestState.responseStatusCode = "302";
                            myRequestState.command = "過去ログ０";
                        }
                        allDone.Set();
                        return;
                    }
                    else if (e.Message.Contains("403") == true)
                    {
                        //ぼぼん規制
                        myRequestState.responseStatusCode = "403";
                        myRequestState.command = "終了";
                        allDone.Set();
                        return;
                    }
                    else if (e.Message.Contains("404") == true)
                    {
                        //見つからない
                        myRequestState.responseStatusCode = "404";
                        myRequestState.command = "過去ログ０";
                        allDone.Set();
                        return;
                    }
                    else if (e.Message.Contains("416") == true)
                    {
                        //あぼーん
                        myRequestState.responseStatusCode = "416";
                        myRequestState.command = "全取得";
                        allDone.Set();
                        return;
                    }
                    else
                    {
                        //それ以外の場合
                        myRequestState.responseStatusCode = "999";
                        myRequestState.command = "終了";
                        Const.debugForm.OutPutDebug("Other", e);
                        return;
                    }
                }
                catch
                {
                    //新規データが無い時にエラーが帰ることがある
                    myRequestState.responseStatusCode = "998";
                    myRequestState.command = "終了";
                    allDone.Set();
                    return;
                }

                Stream responseStream = myRequestState.response.GetResponseStream();
                myRequestState.streamResponse = responseStream;

                try
                {
                    Stream st = myRequestState.streamResponse;
                    //文字列データ
                    StreamReader sr;

                    if (Util.TYPE_JBBS == type)
                    {
                        sr = new StreamReader(st, System.Text.Encoding.GetEncoding("EUC-JP"));
                    }
                    /*else if (Util.TYPE_OPEN2CH == type)
                    {
                        sr = new StreamReader(st, System.Text.Encoding.UTF8);
                    }*/
                    else
                    {
                        sr = new StreamReader(st, System.Text.Encoding.GetEncoding("shift-jis"));
                    }
                    string str;

                    str = sr.ReadToEnd();
                    myRequestState.responseData.Append(str);
                }
                catch { ;}

                IAsyncResult asynchronousInputRead = responseStream.BeginRead(myRequestState.BufferRead, 0, BUFFER_SIZE, new AsyncCallback(ReadCallBack), myRequestState);
                return;
            }
            catch
            { ;}
        }

        private void ReadCallBack(IAsyncResult asyncResult)
        {
            RequestState myRequestState = (RequestState)asyncResult.AsyncState;
            Stream st = myRequestState.streamResponse;

            StreamReader sr;

            if (Util.TYPE_JBBS == type)
            {
                sr = new StreamReader(st, System.Text.Encoding.GetEncoding("EUC-JP"));
            }
            /*else if (Util.TYPE_OPEN2CH == type)
            {
                sr = new StreamReader(st, System.Text.Encoding.UTF8);
            }*/
            else
            {
                sr = new StreamReader(st, System.Text.Encoding.GetEncoding("shift-jis"));
            }

            string str;
            try
            {
                str = sr.ReadToEnd();

                if (str.Length > 0)
                {
                    myRequestState.responseData.Append(str);
                    try
                    {
                        IAsyncResult asynchronousResult = st.BeginRead(myRequestState.BufferRead, 0, BUFFER_SIZE, new AsyncCallback(ReadCallBack), myRequestState);
                        sr.Close();
                        st.Close();
                        return;
                    }
                    catch
                    {
                    }
                }
                else
                {
                }
            }
            catch { ;}

            if (myRequestState.responseData.Length > 1)
            {
                //バイナリデータ

                //文字列データ
                this.httpResData = myRequestState.responseData.ToString();

                if (Util.TYPE_JBBS == type)
                {
                    Encoding eucEnc = Encoding.GetEncoding("EUC-JP");
                    this.httpResRawDataLength = eucEnc.GetByteCount(this.httpResData);
                }
                else
                {
                    Encoding sjisEnc = Encoding.GetEncoding("Shift_JIS");
                    this.httpResRawDataLength = sjisEnc.GetByteCount(this.httpResData);
                    //System.Text.Encoding.GetEncoding(932).GetString(myRequestState.memoryStream.ToArray());
                }
            }

            myRequestState.streamResponse.Close();
            //myRequestState.response.Close();
            sr.Close();
            st.Close();

            allDone.Set();
        }
    }
}