/// <summary>
 /// <seealso cref="EpisodeInfo"/>의 새 인스턴스를 초기화합니다.
 /// </summary>
 /// <param name="title">회차 제목</param>
 /// <param name="episodeNo">에피소드 번호</param>
 /// <param name="imageUrls"></param>
 /// <param name="date">웹툰 등록일</param>
 public EpisodeInfo(EpisodeKey episodeKey, string title, string[] imageUrls, string date)
 {
     EpisodeTitle     = title;
     EpisodeNo        = episodeKey.EpisodeNo;
     EpisodeImageUrls = imageUrls;
     EpisodeDate      = date;
 }
        /// <summary>
        /// episodeKey가 지정하는 회차의 폴더 이름을 정의된 포맷 형식(<seealso cref="Config.EpisodeDirectoryNameFormat"/>)에 따라 생성합니다.
        /// </summary>
        /// <param name="episodeKey"></param>
        /// <returns></returns>
        private static string BuildEpisodeDirectoryName(WebtoonInfo webtoonInfo, EpisodeKey episodeKey)
        {
            string titleId      = episodeKey.TitleId;
            int    episodeNo    = episodeKey.EpisodeNo;
            string date         = webtoonInfo.Episodes[episodeKey.EpisodeNo].EpisodeDate;
            string webtoonTitle = webtoonInfo.WebtoonTitle;
            string episodeTitle = webtoonInfo.Episodes[episodeKey.EpisodeNo].EpisodeTitle;

            return(string.Format(config.EpisodeDirectoryNameFormat,
                                 titleId,
                                 episodeNo,
                                 date,
                                 ReplaceFolderName(webtoonTitle),
                                 ReplaceFolderName(episodeTitle)));
        }
        /// <summary>
        /// episodeKey가 지정하는 회차의 폴더 이름을 정의된 포맷 형식(<seealso cref="Config.EpisodeDirectoryNameFormat"/>)에 따라 생성합니다.
        /// </summary>
        /// <param name="episodeKey"></param>
        /// <returns></returns>
        public string BuildEpisodeDirectoryName(EpisodeKey episodeKey)
        {
            string titleId       = episodeKey.TitleId;
            int    episodeNo     = episodeKey.EpisodeNo;
            string date          = webtoonInfo.Episodes[episodeKey.EpisodeNo].EpisodeDate;
            string webtoonTitle  = webtoonInfo.WebtoonTitle;
            string episodeTitle  = webtoonInfo.Episodes[episodeKey.EpisodeNo].EpisodeTitle;
            string webtoonWriter = webtoonInfo.WebtoonWriter;

            return(string.Format(ReplaceFolderName(config.EpisodeDirectoryNameFormat),
                                 titleId,
                                 episodeNo,
                                 date,
                                 ReplaceFolderName(webtoonTitle),
                                 ReplaceFolderName(episodeTitle),
                                 ReplaceFolderName(webtoonWriter)));
        }
        /// <summary>
        /// "{0}({1}) [{2}/{3}] ({4:P}) [{5}]"
        /// <code>{0}:웹툰 제목</code>
        /// <code>{1}:웹툰 아이디</code>
        /// <code>{2}:현재 포지션</code>
        /// <code>{3}:총 작업수</code>
        /// <code>{4}:퍼센트</code>
        /// <code>{5}:회차 날짜</code>
        /// </summary>
        /// <param name="webtoonInfo"></param>
        /// <param name="ProgressTextFormat"></param>
        public static void UpdateWebtoonInfo(WebtoonInfo webtoonInfo, string ProgressTextFormat)
        {
            Agent agent = Agent.Instance;

            Parser.Parser parser     = Parser.Parser.Instance;
            WebtoonKey    webtoonKey = new WebtoonKey(webtoonInfo.WebtoonTitleId);

            //comic.naver.com에서 최신 회차의 EpisodeNo를 불러옵니다.
            agent.LoadPage(webtoonKey.BuildUrl());
            int latestEpisodeNo = int.Parse(parser.GetLatestEpisodeNo());
            //webtoonInfo중 가장 마지막 회차의 EpisodeNo를 불러옵니다.
            int lastEpisodeNo = webtoonInfo.GetLastEpisodeNo();

            //웹툰 정보를 업데이트합니다.
            for (int episodeNo = lastEpisodeNo + 1; episodeNo <= latestEpisodeNo; episodeNo++)
            {
                EpisodeKey episodeKey = new EpisodeKey(webtoonKey.TitleId, episodeNo);
                agent.LoadPage(episodeKey.BuildUrl());
                string currentEpisodeNo = parser.GetCurrentEpisodeNo();
                if (!currentEpisodeNo.Equals(episodeNo.ToString()))
                {
                    //비어있는 번호 건너뛰기
                    continue;
                }
                string   episodeTitle = parser.GetEpisodeTitle();
                string   episodeDate  = parser.GetEpisodeDate();
                string[] imageUrls    = parser.GetComicContentImageUrls();
                webtoonInfo.Episodes.Add(episodeNo, new EpisodeInfo(episodeKey, episodeTitle, imageUrls, episodeDate));
                ProgressChangedEvent(string.Format(ProgressTextFormat,
                                                   webtoonInfo.WebtoonTitle,
                                                   webtoonInfo.WebtoonTitleId,
                                                   (episodeNo).ToString("D" + latestEpisodeNo.ToString().Length.ToString()),
                                                   latestEpisodeNo,
                                                   (decimal)(episodeNo) / latestEpisodeNo,
                                                   webtoonInfo.Episodes[episodeNo].EpisodeDate,
                                                   webtoonInfo.Episodes[episodeNo].EpisodeTitle));
            }
            Console.WriteLine();
        }
        /// <summary>
        /// "{0}({1}) [{2}/{3}] ({4:P}) [{5}]"
        /// <code>{0}:웹툰 제목</code>
        /// <code>{1}:웹툰 아이디</code>
        /// <code>{2}:현재 포지션</code>
        /// <code>{3}:총 작업수</code>
        /// <code>{4}:퍼센트</code>
        /// <code>{5}:회차 날짜</code>
        /// </summary>
        /// <param name="webtoonInfo"></param>
        /// <param name="ProgressTextFormat"></param>
        public void UpdateWebtoonInfo(string ProgressTextFormat, IProgress <string> progress)
        {
            WebtoonKey webtoonKey = new WebtoonKey(webtoonInfo.WebtoonTitleId);

            //comic.naver.com에서 최신 회차의 EpisodeNo를 불러옵니다.
            agent.LoadPage(webtoonKey.BuildUrl());
            int latestEpisodeNo = int.Parse(parser.GetLatestEpisodeNo());
            //webtoonInfo중 가장 마지막 회차의 EpisodeNo를 불러옵니다.
            int lastEpisodeNo = webtoonInfo.GetLastEpisodeNo();

            //웹툰 정보를 업데이트합니다.
            for (int episodeNo = lastEpisodeNo + 1; episodeNo <= latestEpisodeNo; episodeNo++)
            {
                EpisodeKey episodeKey = new EpisodeKey(webtoonKey.TitleId, episodeNo);
                agent.LoadPage(episodeKey.BuildUrl());
                string currentEpisodeNo = parser.GetCurrentEpisodeNo();
                if (!currentEpisodeNo.Equals(episodeNo.ToString()))
                {
                    //비어있는 번호 건너뛰기
                    continue;
                }
                string   episodeTitle = parser.GetEpisodeTitle();
                string   episodeDate  = parser.GetEpisodeDate();
                string[] imageUrls    = parser.GetComicContentImageUrls();
                webtoonInfo.Episodes.Add(episodeNo, new EpisodeInfo(episodeKey, episodeTitle, imageUrls, episodeDate));
                progress.Report(string.Format(ProgressTextFormat,
                                              webtoonInfo.WebtoonTitle,
                                              webtoonInfo.WebtoonTitleId,
                                              (episodeNo).ToString("D" + latestEpisodeNo.ToString().Length.ToString()),
                                              latestEpisodeNo,
                                              (decimal)(episodeNo) / latestEpisodeNo,
                                              webtoonInfo.Episodes[episodeNo].EpisodeDate,
                                              webtoonInfo.Episodes[episodeNo].EpisodeTitle));
            }
            Thread.Sleep(500);
        }
        /// <summary>
        /// <code>{0}:웹툰 제목</code>
        /// <code>{1}:웹툰 아이디</code>
        /// <code>{2}:현재 포지션</code>
        /// <code>{3}:총 작업수</code>
        /// <code>{4}:퍼센트</code>
        /// <code>{5}:회차 날짜</code>
        /// <code>{6}:회차 제목</code>
        /// <code>{7}:이미지 인덱스</code>
        /// <code>{8}:이미지 파일명</code>
        /// <code>{9}:다운받은 메가바이트 용량</code>
        /// </summary>
        /// <param name="webtoonInfo"></param>
        /// <param name="imageKeys"></param>
        /// <param name="ProgressTextFormat"></param>
        public static void Download(WebtoonInfo webtoonInfo, ImageKey[] imageKeys, string ProgressTextFormat)
        {
            IO.taskEnd = false;
            Task  t     = new Task(new Action(() => { IO.SaveFileAsync(); }));
            Agent agent = Agent.Instance;
            long  size  = 0;

            t.Start();
            for (int i = 0; i < imageKeys.Length; i++)
            {
                EpisodeKey episodeKey = new EpisodeKey(imageKeys[i].TitleId, imageKeys[i].EpisodeNo);
                agent.SetHeader("Referer", episodeKey.BuildUrl());
                byte[] buff = agent.DownloadData(webtoonInfo.Episodes[imageKeys[i].EpisodeNo].EpisodeImageUrls[imageKeys[i].ImageIndex]);

                IO.WriteAllBytes(
                    BuildImageFileFullDirectory(webtoonInfo, imageKeys[i]),
                    BuildImageFileName(webtoonInfo, imageKeys[i]),
                    buff);
                size += buff.Length;
                ProgressChangedEvent(string.Format(ProgressTextFormat,
                                                   webtoonInfo.WebtoonTitle,
                                                   webtoonInfo.WebtoonTitleId,
                                                   i + 1,
                                                   imageKeys.Length,
                                                   (double)(i + 1) / imageKeys.Length,
                                                   webtoonInfo.Episodes[imageKeys[i].EpisodeNo].EpisodeDate,
                                                   webtoonInfo.Episodes[imageKeys[i].EpisodeNo].EpisodeTitle,
                                                   imageKeys[i].ImageIndex,
                                                   BuildImageFileName(webtoonInfo, imageKeys[i]),
                                                   (double)size / 1048576
                                                   ));
            }
            IO.taskEnd = true;
            t.Wait();
            Console.WriteLine();
        }
        public override void Start(params string[] args)
        {
            if (args.Length == 0)
            {
                IO.PrintError("titleId를 입력해주세요");
                return;
            }
            List <WebtoonKey> keys   = new List <WebtoonKey>();
            List <string>     titles = new List <string>();

            for (int i = 0; i < args.Length; i++)
            {
                if (!int.TryParse(args[i], out _))
                {
                    IO.PrintError("titleId는 숫자입니다. : " + args[i]);
                    return;
                }
                agent.LoadPage(string.Format("https://comic.naver.com/webtoon/list.nhn?titleId={0}", args[i]));
                string title = parser.GetWebtoonTitle();
                if (title == "네이버 웹툰")
                {
                    IO.PrintError("존재하지 않는 titleId입니다. : " + args[i]);
                    return;
                }
                IO.Print(string.Format("{2}. {1}($${0}$cyan$) 대기..", args[i], title, i + 1), true, true);
                keys.Add(new WebtoonKey(args[i]));
                titles.Add(title);
            }
            for (int i = 0; i < keys.Count; i++)
            {
                Downloader downloader;
                IO.Print("");
                WebtoonInfo webtoonInfo;

                if (IO.Exists("Cache", keys[i].TitleId + ".json"))
                {
                    webtoonInfo = JsonConvert.DeserializeObject <WebtoonInfo>(IO.ReadTextFile("Cache", keys[i].TitleId + ".json"));
                    downloader  = new Downloader(webtoonInfo, config);
                    int latest = int.Parse(parser.GetLatestEpisodeNo());
                    int last   = webtoonInfo.GetLastEpisodeNo();
                    IO.Print(string.Format("{2}. {0}($${1}$cyan$) URl 캐시를 불러왔습니다.", webtoonInfo.WebtoonTitle, keys[i].TitleId, i + 1), true, true);
                    IO.Print(string.Format("{2}. {0}($${1}$cyan$) 업데이트된 회차를 확인합니다.. ", webtoonInfo.WebtoonTitle, keys[i].TitleId, i + 1), true, true);
                    if (latest != last)
                    {
                        IO.Print(string.Format("{4}. {0}($${1}$cyan$) URl 캐시를 업데이트합니다.. [no($${2}$cyan$) ~ no($${3}$cyan$)]", webtoonInfo.WebtoonTitle, keys[i].TitleId, last + 1, latest, i + 1), true, true);
                        downloader.UpdateWebtoonInfo((i + 1).ToString() + ". {0}($${1}$cyan$) [{2}/{3}] ($${4:P}$green$) [{5}]", progress);
                        Console.WriteLine();
                        IO.Print(string.Format("{2}. {0}($${1}$cyan$) URl 캐시에 업데이트된 회차를 추가하였습니다.", webtoonInfo.WebtoonTitle, keys[i].TitleId, i + 1), true, true);
                    }
                    else
                    {
                        IO.Print(string.Format("{2}. {0}($${1}$cyan$) 업데이트된 회차가 없습니다. ", webtoonInfo.WebtoonTitle, keys[i].TitleId, i + 1), true, true);
                    }
                    if (string.IsNullOrWhiteSpace(webtoonInfo.WebtoonWriter))
                    {
                        EpisodeKey episodeKey = new EpisodeKey(keys[i].TitleId, 1);
                        agent.LoadPage(episodeKey.BuildUrl());
                        string webtoonWriter = parser.GetWebtoonWriter();
                        webtoonInfo.WebtoonWriter = webtoonWriter;
                    }
                    var tuple = downloader.GetDownloadedImagesInformation();

                    if (tuple.downloadedImageCount != 0)
                    {
                        IO.Print(string.Format("{4}. {0}($${1}$cyan$) 이미 다운로드된 이미지 $${2}$cyan$장 ($${3:0.00}$blue$ MB)  ", webtoonInfo.WebtoonTitle, keys[i].TitleId, tuple.downloadedImageCount, (double)tuple.downloadedImagesSize / 1048576, i + 1), true, true);
                    }
                }
                else
                {
                    webtoonInfo = new WebtoonInfo(keys[i], titles[i]);
                    downloader  = new Downloader(webtoonInfo, config);
                    IO.Print(string.Format("{2}. {0}($${1}$cyan$) URl 캐시를 생성합니다.", webtoonInfo.WebtoonTitle, keys[i].TitleId, i + 1), true, true);
                    downloader.UpdateWebtoonInfo((i + 1).ToString() + ". {0}($${1}$cyan$) [{2}/{3}] ($${4:P}$green$) [{5}]", progress);
                    Console.WriteLine("");
                    IO.Print(string.Format("{2}. {0}($${1}$cyan$) URl 캐시를 생성하였습니다..", webtoonInfo.WebtoonTitle, keys[i].TitleId, i + 1), true, true);
                }

                if (string.IsNullOrWhiteSpace(webtoonInfo.WebtoonWriter))
                {
                    EpisodeKey episodeKey = new EpisodeKey(keys[i].TitleId, 1);
                    agent.LoadPage(episodeKey.BuildUrl());
                    string webtoonWriter = parser.GetWebtoonWriter();
                    webtoonInfo.WebtoonWriter = webtoonWriter;
                }

                IO.WriteTextFile("Cache", keys[i].TitleId + ".json", JsonConvert.SerializeObject(webtoonInfo));

                ImageKey[] imageKeys = downloader.BuildImageKeysToDown();
                if (imageKeys.Length == 0)
                {
                    IO.Print(string.Format("{2}. {0}($${1}$cyan$) 모든 이미지가 다운로드되었습니다..추가로 다운로드할 이미지가 존재하지 않습니다.", webtoonInfo.WebtoonTitle, keys[i].TitleId, i + 1), true, true);
                    return;
                }
                IO.Print(string.Format("{2}. {0}($${1}$cyan$) 다운로드를 시작합니다. ", webtoonInfo.WebtoonTitle, keys[i].TitleId, i + 1), true, true);
                var task = Task.Run(() => downloader.DownloadAsync(imageKeys, (i + 1).ToString() + ". {0}($${1}$cyan$) [{2}/{3}] ($${9:0.00}$blue$ MB) ($${4:P}$green$) [{5}]", progress));
                task.Wait();
                Console.WriteLine();
                IO.Print(string.Format("{2}. {0}($${1}$cyan$) 다운로드 완료", webtoonInfo.WebtoonTitle, keys[i].TitleId, i + 1), true, true);
            }
        }