using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Xml.Serialization;
using System.IO;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Web;
using System.Runtime.Serialization;
using System.Diagnostics;
using pcmp.Forms;

namespace pcmp.Setting
{
    [Serializable()]
    public class YpSetting : SerializableSetting, ICloneable
    {
        const string fileName = "YelloPages.xml";

        public YpSetting() { }

        public YpSetting(SerializationInfo info, StreamingContext context)
        {
            Serializer.Deserialize(this, info);
        }

        public static void Save(YpSetting[] yps)
        {
            Serializer.Serialize(fileName, yps, true);
        }

        public static YpSetting[] Load()
        {
            YpSetting[] obj =
                Serializer.Deserialize(fileName, typeof(YpSetting[])) as YpSetting[];
            return obj;
        }

        // ファイルへ保存
        public bool EnableYP = true;
        public string YpName = string.Empty;
        public string YpAddr = string.Empty;
        public Color TextColor = Color.FromKnownColor(KnownColor.WindowText);
        public Color BackColor = Color.FromKnownColor(KnownColor.Window);
        public Font Font;
        public Color ContactColor = Color.Blue;
        public FontStyle ContactFontStyle = 0;
        public Color DirectColor = Color.Blue;
        public FontStyle DirectFontStyle = 0;
        public bool ShowNewBalloon = true;
        public string YpNamespace = string.Empty;
        public string Encoding = string.Empty;
        public bool UseEncoding;
        public bool UseBandwidthTool;
        public string BandwidthToolPath;
        public string BandwidthToolArg;
        public bool UseScript;
        public string ScriptPath = string.Empty;

        [NonSerialized]
        public List<Channel> Channels = new List<Channel>();
        [NonSerialized]
        public List<Channel> Disconnected = new List<Channel>();
        [NonSerialized]
        public bool GetFromPCRaw = false;
        [NonSerialized]
        public bool GetError = false;
        [NonSerialized]
        public IntPtr PCRawHandle = IntPtr.Zero;
        [NonSerialized]
        public bool HaveIndexTextStream = false;
        [NonSerialized]
        public string IndexTextStreamUrl = string.Empty;
        [NonSerialized]
        public uint LastUpdate;
        [NonSerialized]
        public DateTime LastHttpAccess = new DateTime(0);
        [NonSerialized]
        public uint PCRawLastUpdate;
        [NonSerialized]
        public string IndexTextChannelName = string.Empty;
        [NonSerialized]
        public HttpStatus LastHttpStatus = new HttpStatus();
        [NonSerialized]
        public int ChannelCount;
        [NonSerialized]
        public bool GetIndexTextNextStreamRef;
        [NonSerialized]
        public IndexTextSource Source;
        [NonSerialized]
        public string IndexTxt = string.Empty;

        #region ICloneable メンバ

        public object Clone()
        {
            return MemberwiseClone();
        }

        #endregion

        public void GetIndexText()
        {
            List<Channel> channels = new List<Channel>();
            DateTime now = DateTime.Now.ToUniversalTime();
            HttpStatus httpStatus = new HttpStatus();
            //string IndexTxt = string.Empty;

            if (Source == IndexTextSource.Both | Source == IndexTextSource.PCRaw)
            {
                IndexTxt = GetIndexTextFromStream();
                httpStatus.StatusCode = "none";
                httpStatus.StatusDesc = "none";
            }

            if (Source == IndexTextSource.Both | Source == IndexTextSource.Http)
            {
                if (string.IsNullOrEmpty(IndexTxt))
                {
                    Trace.WriteLine(string.Format("HTTPよりindex.txtを取得({0})", YpName));
                    IndexTxt = GetIndexTextFromHttp(out httpStatus);
                    GetFromPCRaw = false;
                    LastUpdate = (uint)(now - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds;
                    LastHttpAccess = now.ToLocalTime();
                }
            }
            LastHttpStatus = httpStatus;

            if (Source == IndexTextSource.Both | Source == IndexTextSource.Http)
            {
                if (string.IsNullOrEmpty(IndexTxt))
                {
                    Trace.WriteLine(string.Format("YP取得エラー({0}:{1}-{2})", YpName, LastHttpStatus.StatusCode, LastHttpStatus.StatusDesc));

                    GetError = true;

                    if (!Settings.GeneralSetting.DoNotShowErrorChannel)
                    {
                        #region エラーアイテム
                        Channel item = new Channel();
                        item.ChannelName = "PCMP2 Error";
                        item.Desc = "[" + httpStatus.StatusCode + "]" + httpStatus.StatusDesc;
                        item.Detail = item.Desc;
                        item.Diff = ChannelDiff.None;
                        item.Alt = "pcmp2 error";
                        item.BitRate = new Bitrate(000);
                        item.Comment = httpStatus.StatusDesc;
                        item.GetTime = now.ToLongTimeString();
                        item.ID = "00000000000000000000000000000000";
                        item.No = 0;
                        item.Status = new Status(0, 0);
                        item.Time = new CastTime(00, 00);
                        item.Type = "ERR";
                        item.YPUrl = YpAddr;
                        item.YPName = YpName;
                        #endregion

                        if (Channels == null) Channels = new List<Channel>();
                        Channels.RemoveAll(SearchErrorChannel);
                        Channels.Add(item);
                    }

                    return;
                }
            }

            if (UseScript)
            {
                FormMain.HostScript.AddGlobal("Channel", null);
                FormMain.HostScript.AddGlobal("GeneralSetting", Settings.GeneralSetting);
                FormMain.HostScript.AddGlobal("FavSettings", Settings.FavSettings);
                FormMain.HostScript.AddGlobal("PlayerSettings", Settings.PlayerSettings);
                FormMain.HostScript.AddGlobal("YpSetting", this);
                FormMain.HostScript.ExecuteFile(ScriptPath);
            }

            GetError = false;
            Regex regexIndex = new Regex("^index(_[a-z]+)?\\.txt$");
            string[] splittext = IndexTxt.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
            ChannelCount = splittext.Length;

            for (int i = 0; i < splittext.Length; i++)
            {
                string channelText = splittext[i];
                Channel item = new Channel();
                string[] sep = { "<>" };
                string[] ch = channelText.Split(sep, StringSplitOptions.None);
                if (ch.Length < 19) continue;
                item.No = i + 1;
                item.ChannelName = HttpUtility.HtmlDecode(ch[0]);

                // ジャンル
                if (!string.IsNullOrEmpty(ch[4]) || !string.IsNullOrEmpty(ch[5]))
                {
                    item.Desc = "[";
                    if (!string.IsNullOrEmpty(ch[4]))
                    {
                        item.Desc += ch[4];
                        if (!string.IsNullOrEmpty(ch[5])) item.Desc += " - ";
                    }
                    if (!string.IsNullOrEmpty(ch[5])) item.Desc += ch[5];

                    item.Desc += "]";
                }

                item.Desc = HttpUtility.HtmlDecode(item.Desc);

                // Playing
                if (!string.IsNullOrEmpty(ch[10]) || !string.IsNullOrEmpty(ch[11]) || !string.IsNullOrEmpty(ch[12]))
                {
                    item.Playing = "Playing:";
                    if (!string.IsNullOrEmpty(ch[10])) item.Playing += ch[10];
                    if (!string.IsNullOrEmpty(ch[11]))
                    {
                        if (!string.IsNullOrEmpty(item.Playing)) item.Playing += " - ";
                        item.Playing += ch[11];
                    }
                    if (!string.IsNullOrEmpty(ch[12]))
                    {
                        if (!string.IsNullOrEmpty(item.Playing)) item.Playing += " - ";
                        item.Playing += ch[12];
                    }
                    if (!string.IsNullOrEmpty(ch[13]))
                    {
                        if (!string.IsNullOrEmpty(item.Playing)) item.Playing += " - ";
                        item.Playing += ch[13];
                    }
                }

                item.Playing = HttpUtility.HtmlDecode(item.Playing);
                item.Detail = item.Desc + " " + item.Playing;
                item.StreamUrl = "http://" + Settings.GeneralSetting.NetworkSetting.HostAddr + "/pls/" + ch[1] + "?tip=" + ch[2];
                item.ContactUrl = ch[3];
                int listener = 0, relay = 0;
                int.TryParse(ch[6], out listener);
                int.TryParse(ch[7], out relay);
                item.Status = new Status(listener, relay);
                int bitrate = 0;
                int.TryParse(ch[8], out bitrate);
                item.BitRate = new Bitrate(bitrate);
                item.Type = ch[9];
                item.ChatUrl = YpAddr + "chat.php?cn=" + ch[14];
                item.StatUrl = YpAddr + "getgmt.php?cn=" + ch[14];

                if (string.IsNullOrEmpty(ch[15]))
                    item.Time = new CastTime(0, 0);
                else
                {
                    string[] hm = ch[15].Split(':');
                    int hour = 0, min = 0;
                    int.TryParse(hm[0], out hour);
                    int.TryParse(hm[1], out min);
                    item.Time = new CastTime(hour, min);
                }
                item.Alt = ch[16];
                item.Comment = HttpUtility.HtmlDecode(ch[17]);
                item.YPUrl = YpAddr;
                item.YPName = YpName;
                item.ID = ch[1];
                item.GetTime = now.ToLongTimeString();
                item.State = ChannelState.Broadcasting;

                // index.txtかどうか判別
                if (regexIndex.IsMatch(item.ChannelName))
                {
                    IndexTextChannelName = item.ChannelName;
                    IndexTextStreamUrl = item.StreamUrl;
                    HaveIndexTextStream = true;

                    // PcrawHandleがIntPtr.Zeroの時だけ開始
                    if (PCRawHandle == IntPtr.Zero)
                        GetIndexTextFromStream();
                }

                channels.Add(item);
            }

            // 各チャンネルの比較
            Disconnected = CheckDifference(channels, Channels);

            Channels = channels;
        }

        private List<Channel> CheckDifference(List<Channel> newChannels, List<Channel> oldChannels)
        {
            if (oldChannels == null)
            {
                // 初回更新
                for (int i = 0; i < newChannels.Count; i++)
                    newChannels[i].Diff = ChannelDiff.None;
                return new List<Channel>();
            }

            List<Channel> disconnect = new List<Channel>(oldChannels);
            foreach (Channel newchannel in newChannels)
            {
                bool set = false;

                foreach(Channel oldchannel in oldChannels)
                {
                    if (oldchannel.ID == newchannel.ID)
                    {
                        set = true;

                        if (newchannel.No < oldchannel.No)
                            newchannel.Diff = ChannelDiff.Up;
                        else if (newchannel.No > oldchannel.No)
                            newchannel.Diff = ChannelDiff.Down;
                        else
                            newchannel.Diff = ChannelDiff.None;

                        disconnect.Remove(oldchannel);

                        break;
                    }
                }

                if (!set)
                {
                    newchannel.Diff = ChannelDiff.New;
                    if (this.ShowNewBalloon)
                    {
                        pcmp.Forms.FormMain.MessageManager.AddChannel(newchannel, "新着チャンネル");
                    }
                }
            }

            return disconnect;
        }

        private string GetIndexTextFromHttp(out HttpStatus httpStatus)
        {
            string url = YpAddr;
            if (!string.IsNullOrEmpty(YpNamespace))
            {
                url += "index.txt?ns=" + YpNamespace;
                url += "&host=" + Settings.GeneralSetting.NetworkSetting.HostAddr;
            }
            else url = YpAddr + "index.txt?host=" + Settings.GeneralSetting.NetworkSetting.HostAddr;
            Encoding enc = System.Text.Encoding.UTF8;
            try
            {
                if (UseEncoding && !string.IsNullOrEmpty(this.Encoding))
                    enc = System.Text.Encoding.GetEncoding(this.Encoding);
            }
            catch { }
            return Net.GetStringFromHttp(url, enc, out httpStatus);
        }

        public string GetIndexTextFromStream()
        {
            if (!File.Exists(Path.Combine(Application.StartupPath, "pcraw.dll")))
                return string.Empty;
            if (!this.HaveIndexTextStream) return string.Empty;

            if (this.PCRawHandle != IntPtr.Zero)
            {
                int error = PCRaw.PCRawGetLastError(PCRawHandle);
                if (error < 0)
                {
                    Trace.WriteLine(string.Format("PCRawエラー({0})", YpName));
                    Trace.WriteLine(string.Format("PCRawハンドルをクローズします({0})", YpName));
                    PCRaw.PCRawClose(PCRawHandle);
                    PCRawHandle = IntPtr.Zero;
                }
                else
                {
                    PCRawLastUpdate = PCRaw.PCRawGetUpdateTime(PCRawHandle);
                }
            }

            if (PCRawLastUpdate > 0 &&
                (new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(PCRawLastUpdate) - LastHttpAccess.ToUniversalTime()).TotalSeconds > 0)
            {
                Trace.WriteLine(string.Format("PCRawストリームよりindex.txtを取得({0})", YpName));

                byte[] buf;
                PCRaw.PCRawDataLock(PCRawHandle);
                try
                {
                    int bufSize = PCRaw.PCRawGetLength(PCRawHandle);
                    buf = new byte[bufSize];
                    PCRaw.PCRawGetData(PCRawHandle, buf, buf.Length);
                    LastUpdate = PCRawLastUpdate;
                }
                finally
                {
                    PCRaw.PCRawDataUnlock(PCRawHandle);
                }

                GetFromPCRaw = true;

                return System.Text.Encoding.UTF8.GetString(buf);
            }

            if (PCRawHandle == IntPtr.Zero && HaveIndexTextStream)
            {
                string url = this.IndexTextStreamUrl.Replace("/pls/", "/stream/");
                PCRawHandle = PCRaw.PCRawCreate(url);
                Trace.WriteLine(string.Format("PCRawハンドル作成({0}) {1}", YpName, url));
            }

            return string.Empty;
        }

        /// <summary>
        /// PCRawストリームに現在より新しいindex.txtがあるかどうか
        /// </summary>
        /// <returns>新しいindex.txtが存在する場合はtrue</returns>
        public bool CheckIndexTextStream()
        {
            // Trace.WriteLine("PCRawストリームのチェック");
            if (!this.HaveIndexTextStream) return false;
            if (!File.Exists(Path.Combine(Application.StartupPath, "pcraw.dll")))
                return false;
            if (!PeerCast.CheckPeerCastProcess())
                PeerCast.StartPeerCast();

            if (this.PCRawHandle != IntPtr.Zero)
            {
                int error = PCRaw.PCRawGetLastError(PCRawHandle);
                if (error < 0)
                {
                    Trace.WriteLine(string.Format("PCRawエラー({0})", YpName));
                    Trace.WriteLine(string.Format("PCRawハンドルをクローズします({0})", YpName));
                    PCRaw.PCRawClose(PCRawHandle);
                    PCRawHandle = IntPtr.Zero;
                    PCRawLastUpdate = 0;
                }
                else
                {
                    PCRawLastUpdate = PCRaw.PCRawGetUpdateTime(PCRawHandle);
                }
            }

            if (PCRawHandle == IntPtr.Zero && HaveIndexTextStream)
            {
                string url = this.IndexTextStreamUrl.Replace("/pls/", "/stream/");
                PCRawHandle = PCRaw.PCRawCreate(url);
                Trace.WriteLine(string.Format("PCRawハンドル作成({0}) {1}", YpName, url));
            }

            return PCRawLastUpdate > 0 && LastUpdate < PCRawLastUpdate;
            // if (ismodified) Trace.WriteLine("PCRawストリームのindex.txtが更新されています。");

            //return ismodified;
        }

        private bool SearchErrorChannel(Channel channel)
        {
            if (channel.Alt == "ERR") return true;
            else return false;
        }
    }

    public class DisplayStyle
    {
        public Color TextColor;
        public Color BackColor;
        public Font Font;
        public FontStyle ContactStyle;
        public Color ContactColor;
        public FontStyle DirectStyle;
        public Color DirectColor;
    }

    public enum IndexTextSource
    {
        Both,
        Http,
        PCRaw
    }
}
