using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.IO;
using System.IO.Compression;

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;

using CSVDownloader.Code;
using CSVDownloader.Store.CreditCSVData;
using CSVDownloader.Store.QRCSVData;
using CSVDownloader.Store.ElectronicMoneyDataStore;

using ExpectedConditions = OpenQA.Selenium.Support.UI.ExpectedConditions;

namespace CSVDownloader.Web {



    abstract class WebController {

        protected log4net.ILog logger_ = log4net.LogManager.GetLogger("");

        protected ChromeDriver driver_;

        protected WebDriverWait wait_;

        private const int interval_milisec_ = 1000;

        protected CreditAgent agent_;

        /// <summary>
        /// 駐車場名とSpotIDのマッピング
        /// </summary>
        protected IDictionary<String, String> dic_;


        public WebController(ChromeDriver driver) {
            driver_ = driver;
            wait_ = new WebDriverWait(driver_, TimeSpan.FromSeconds(10));
        }

        public virtual void SetParkingDic(IDictionary<String, String> dic) {
            dic_ = dic;
        }

        public CreditAgent GetCreditAgent() {
            return agent_;
        }

        abstract public ResultCode Login(LoginInfo login_info);

        abstract public ResultCode Download(DateTime from, DateTime to);

        abstract public ResultCode Logout();

        public ResultCode Archive(String archive_filename) {

            logger_.Info("アーカイブ開始");

            var dir_name = "archive";
            if (!Directory.Exists(dir_name)) {
                Directory.CreateDirectory(dir_name);
            }


            String target = @$"{dir_name}\{archive_filename}";
            logger_.Info($"{target}");

            if (System.IO.File.Exists(target)) {
                logger_.Info($"上書き:{target}");
                System.IO.File.Delete(target);
            }
            ZipFile.CreateFromDirectory("download", target);
            logger_.Info("アーカイブ終了");

            return ResultCode.OK;
        }

        public virtual List<CreditCSVData> GetCreditCSVDataList() {
            return new List<CreditCSVData>();
        }
        public virtual List<QRCSVData> GetQRCSVDataList() {
            return new List<QRCSVData>();
        }

        public virtual List<ElectronicMoneyCSVData> GetElectronicMoneyCSVDataList() {
            return new List<ElectronicMoneyCSVData>();
        }

        protected void Click(String xpath, bool wait = true) {

            if (wait) {
                var ele = wait_.Until(ExpectedConditions.ElementToBeClickable(By.XPath(xpath)));
                ele.Click();

            } else {
                var ele = driver_.FindElementByXPath(xpath);
                ele.Click();
            }

            Console.WriteLine($"Click {xpath}");
            Wait(interval_milisec_);
        }

        protected void Click(IWebElement ele) {
            ele.Click();
            Wait(interval_milisec_);
        }

        protected void Send(String xpath, String value, bool enter = false) {
            var ele = wait_.Until(ExpectedConditions.ElementIsVisible(By.XPath(xpath)));

            ele.SendKeys(value);

            if (enter) {
                ele.SendKeys(OpenQA.Selenium.Keys.Enter);
            }
            Wait(interval_milisec_);
        }

        protected void Clear(String xpath) {
            var ele = wait_.Until(ExpectedConditions.ElementExists(By.XPath(xpath)));

            ele.Clear();

            Wait(interval_milisec_);
        }

        protected void Select(String xpath, String value) {
            try {

                var ele = wait_.Until(ExpectedConditions.ElementIsVisible(By.XPath(xpath)));

                var select = new SelectElement(ele);

                var options = select.Options;

                select.SelectByValue(value);
                Wait(interval_milisec_);
            } catch (Exception e) {
                Console.WriteLine($" Select failed xpath:{xpath} value:{value}");
                throw e;
            }

        }

        /// <summary>
        /// ダウンロードを開始したファイルを完了まで監視する
        /// 完了後はダウンロード完了ディレクトリに移動する
        /// </summary>
        /// <returns></returns>
        protected void WaitForDownload(String filename) {
            String tmpdownload_dir = DriverFactory.GetTmpDownloadDir();
            String download_dir = DriverFactory.GetDownloadDir();

            int limit_start = 0;
            long last_size = 0;
            String complete_filename = "";

            while (true) {
                var dir = new DirectoryInfo(tmpdownload_dir);
                var files = dir.GetFiles();



                if (files.Length != 0) {
                    var file = files[0];


                    if (file.Name.Contains(".crdownload") || file.Name.Contains(".tmp")) {
                        // ダウンロード継続
                        last_size = file.Length;
                        logger_.Info($"ダウンロード監視中:{file.Name}  size:{file.Length}");
                    } else if (file.Length == last_size) {
                        // ダウンロード完了
                        logger_.Info($"ダウンロード完了:{file.Name}  size:{file.Length}");
                        complete_filename = file.Name;
                        break;
                    } else {
                        // ダウンロード継続
                        last_size = file.Length;
                        logger_.Info($"ダウンロード監視中:{file.Name}  size:{file.Length}");
                    }
                } else {
                    // ファイルが存在しない場合は、一定回数リトライし
                    // 規定回数存在しない場合は失敗とする。
                    if (10 < limit_start) {
                        throw new Exception("ダウンロード開始失敗");
                    }
                    logger_.Info($"ダウンロード開始待ち");

                    limit_start++;
                }
                Wait(500);
            }


            // ファイルの移動
            String source_filename = $@"{tmpdownload_dir}\{complete_filename}";
            String dest_filename = $@"{download_dir}\{filename}";
            System.IO.File.Move(source_filename, dest_filename, true);

        }

        protected void Wait(int milisec) {
            Thread.Sleep(milisec);
        }

        protected String GetSpotID(String parking_name) {

            if (!dic_.ContainsKey(parking_name)) {
                throw new ArgumentException();
            }

            return dic_[parking_name];

        }

        protected class CSVConfig {
            public bool header = false;
            public List<String> remove_char = new List<string>();
        }

        protected List<String[]> ReadCsv(String csvpath, CSVConfig config, Encoding enc) {

            var list = new List<String[]>();

            var fs = new FileStream(csvpath, FileMode.Open);
            var data = new byte[fs.Length];
            fs.Read(data, 0, data.Length);
            fs.Close();


            var content = enc.GetString(data);

            var lines = content.Split("\n");

            bool header_skip = config.header;

            foreach (var line in lines) {

                var tmpline = line;

                line.Trim();

                // 空の行はスキップ
                if (line.Length == 0) {
                    continue;
                }

                // ヘッダー行のスキップ制御
                if (header_skip) {
                    header_skip = false;
                    continue;
                }

                // 囲み文字の削除
                if (config.remove_char != null) {
                    foreach (var remove_char in config.remove_char) {
                        tmpline = tmpline.Replace(remove_char, "");
                    }
                }

                var values = tmpline.Split(",");
                var elements = new String[values.Length];
                int index = 0;
                foreach (var ele in values) {
                    elements[index++] = ele.Trim();
                }
                list.Add(elements);
            }


            return list;
        }
        /// <summary>
        /// デフォルトのフレームにもどる
        /// </summary>
        protected void SwitchToFrame() {
            driver_.SwitchTo().DefaultContent();
        }

        /// <summary>
        /// フレームを切り替える。
        /// </summary>
        /// <param name="xpath"></param>
        protected void SwitchToFrame(String xpath) {
            var frame = wait_.Until(ExpectedConditions.ElementExists(By.XPath(xpath)));
            driver_.SwitchTo().Frame(frame);
        }

        /// <summary>
        /// 空の可能性があるソースをDateTimeへ変換する際、Null許容型として変換する。
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        protected DateTime? GetDateTime(String source) {

            if (source != null && 0 < source.Length) {
                return DateTime.Parse(source);
            } else {
                return null;
            }

        }


        protected void CloseAlert() {
            try {

                var alert = wait_.Until(ExpectedConditions.AlertIsPresent());
                alert.Accept();


            } catch (Exception) {
                logger_.Info("アラート表示なし");
                return;
            }
        }
    }
}
