//ライセンス情報は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 AgeOnUpdate
{
    public class RequestState
    {
        // This class stores the State of the request.
        const int BUFFER_SIZE = 4096;
        public StringBuilder responseData;
        public byte[] BufferRead;
        public HttpWebRequest request;
        public HttpWebResponse response;
        public Stream streamResponse;
        public MemoryStream memoryStream;
        public int offset = 0;
        public string responseStatusCode = "";
        public string command = "";
        public RequestState()
        {
            BufferRead = new byte[BUFFER_SIZE];
            responseData = new StringBuilder("");
            memoryStream = new MemoryStream();
            request = null;
            streamResponse = null;
        }
    }

    class DownloadBinary
    {
        private int donwloadedSize = 0;
        private ManualResetEvent allDone;

        public string command = "";

        const int BUFFER_SIZE = 4096;

        byte[] byteData;

        UpdateFileData updateFileData;
        ProgressBarForm progressBarForm;

        public DownloadBinary()
        {
        }
        public byte[] DownloadBinaryMethod(UpdateFileData updateFileData, ProgressBarForm progressBarForm)
        {
            allDone = new ManualResetEvent(false);

            this.updateFileData = updateFileData;
            this.progressBarForm = progressBarForm;

            Uri u = new Uri(updateFileData.fileUrl);

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

            req.AutomaticDecompression = DecompressionMethods.GZip;
            //req.Timeout = 3000;
            req.AllowAutoRedirect = true;

            req.Proxy = null;

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


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

            allDone.WaitOne();

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

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

            //--受信したデータを見る
            if ((byteData == null) || (byteData.Length < 1))
            {
                myRequestState.response.Close();
                return null;
            }

            myRequestState.response.Close();
            return byteData;
        }


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

                if (myHttpWebRequest.HaveResponse == false)
                {
                    Thread.Sleep(1);
                }

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


                    if (myRequestState.response.StatusCode == HttpStatusCode.OK)
                    {
                        myRequestState.responseStatusCode = "200";
                        myRequestState.command = "終了";
                    }
                }
                catch (System.Net.WebException e)
                {
                    myRequestState.responseStatusCode = "404";
                    myRequestState.command = "終了";
                    allDone.Set();
                    return;
                }
                catch
                {
                    //新規データが無い時にエラーが帰ることがある
                    myRequestState.responseStatusCode = "998";
                    myRequestState.command = "終了";
                    allDone.Set();
                    return;
                }
                Stream responseStream = myRequestState.response.GetResponseStream();
                myRequestState.streamResponse = responseStream;
                Stream st = myRequestState.streamResponse;
                try
                {
                    //読み込んでしまった本文を読み込む
                    //バイナリデータ
                    byte[] array = ReadBinaryData(st);
                    myRequestState.memoryStream.Write(array, myRequestState.offset, array.Length);
                    myRequestState.offset += array.Length;
                }
                catch
                {
                    //謎のエラー
                    myRequestState.responseStatusCode = "999";
                    myRequestState.command = "終了";
                    allDone.Set();
                    return; ;
                }

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


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

            byte[] array;
            try
            {
                //バイナリデータ
                array = ReadBinaryData(st);
                myRequestState.memoryStream.Write(array, myRequestState.offset, array.Length);
                myRequestState.offset += array.Length;

                //プログレスバーを計算して表示
                double par = myRequestState.offset / updateFileData.fileSize;
                par = System.Math.Pow(par, 2);
                if (par > 0.01)
                {
                    progressBarForm.popUp.Invoke((Action)(() => progressBarForm.progressBar.Value = (int)(par * 100)));
                }

                if (array.Length > 0)
                {
                    try
                    {
                        IAsyncResult asynchronousResult = st.BeginRead(myRequestState.BufferRead, 0, BUFFER_SIZE, new AsyncCallback(ReadCallBack2), myRequestState);
                        st.Close();
                        return;
                    }
                    catch
                    { ;}
                }
            }
            catch
            { ;}

            if (myRequestState.offset > 1)
            {
                //バイナリデータ
                byteData = myRequestState.memoryStream.GetBuffer();;
            }

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

            allDone.Set();
        }

        public byte[] ReadBinaryData(Stream st)
        {
            byte[] buf = new byte[32768]; // 一時バッファ
            int count = 0;

            using (MemoryStream ms = new MemoryStream())
            {

                while (true)
                {
                    count += 1;
                    // ストリームから一時バッファに読み込む
                    int read = st.Read(buf, 0, buf.Length);

                    this.donwloadedSize += read;

                    if (count % 10 == 0)
                    {
                        double par = (double)(this.donwloadedSize) / (double)(updateFileData.fileSize);
                        par = System.Math.Pow(par, 2);
                        if (par > 0.01)
                        {
                            string text = string.Format(
                                "ファイルを受信しています　{0}byte/{1}byte",this.donwloadedSize.ToString(),updateFileData.fileSize.ToString());
                            progressBarForm.popUp.Invoke((Action)(() => progressBarForm.textLabel.Text = text));
                            progressBarForm.popUp.Invoke((Action)(() => progressBarForm.progressBar.Value = (int)(par * 100)));
                        }
                    }

                    if (read > 0)
                    {
                        // 一時バッファの内容をメモリ・ストリームに書き込む
                        ms.Write(buf, 0, read);
                    }
                    else
                    {
                        break;
                    }
                }
                // メモリ・ストリームの内容をバイト配列に格納
                return ms.ToArray();
            }
        }
    }
}