using System;
using System.Linq;
using System.Windows.Forms;
using Gecko;
using System.Reflection;

namespace myBuiltInMethod
{

    public class BrowserScriptInterfaces
    {

        public GeckoWebBrowser browserInstance { get; set; }
        public bool b_navigatingFired { get; set; }
        public bool b_DocCompleted { get; set; }
        public string errmsg { set; get; }
        public GeckoMarkupDocumentViewer ZoomViewer;

        public string strEventScript_DocComp;
        public string strEventScript_Navigating;
        public string strEventScript_AfterDocComp;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public BrowserScriptInterfaces( )
        {
        }


        public void init(GeckoWebBrowser browser)
        {
            browserInstance = browser;
            b_DocCompleted = true;
            b_navigatingFired = false;
        }

        public void BrowserReset()
        {
            Zoom();
            SetUserAgent();
        }


        /// <summary>
        /// 対象ブラウザとの結びつけをおこなう
        /// </summary>
        /// <param name="browser"></param>
        /// <summary>
        /// 対象ブラウザとの結びつけをおこなう
        /// </summary>
        /// <param name="browser"></param>
        [ExportMethod()]
        public void InitWeblib(GeckoWebBrowser browser)
        {
            init(browser);
        }


        /// <summary>
        /// ブラウザのDocumentCompleteイベントにて、実行するスクリプトを注入する
        /// </summary>
        /// <param name="script"></param>
        [ExportMethod()]
        public void SetEventScript_DocComp(string script)
        {
            strEventScript_DocComp = script;
        }


        ///// <summary>
        ///// 対象ブラウザとの結びつけをおこなう
        ///// </summary>
        ///// <param name="browser"></param>
        //[ExportMethod()]
        //public void test(object scripthandler)
        //{
        //    EventInfo evDocComp = browserInstance.GetType().GetEvent("DocumentCompleted");
        //    Type tDelegate = evDocComp.EventHandlerType;

        //    //MethodInfo miHandler = typeof(BrowserScriptInterfaces).GetMethod("LuckyHandler", BindingFlags.NonPublic | BindingFlags.Static);

        //    MethodInfo miAddHandler = evDocComp.GetAddMethod();
        //    object[] addHandlerArgs = { scripthandler };
        //    miAddHandler.Invoke(browserInstance, addHandlerArgs);
        //}

        //public void Doccomphandler(object sender , System.EventArgs e)
        //{

        //}


        /// <summary>
        /// ブラウザのNavigatingイベントにて、実行するスクリプトを注入する
        /// </summary>
        /// <param name="script"></param>
        [ExportMethod()]
        public void SetEventScript_Navigating(string script)
        {
            strEventScript_Navigating = script;
        }

        /// <summary>
        /// ブラウザのDocumentComleteイベント後、タイマーで発火する
        /// AfterDocCompleteにて、実行するスクリプトを注入する
        /// </summary>
        /// <param name="script"></param>
        [ExportMethod()]
        public void SetEventScript_AfterDocComp(string script)
        {
            strEventScript_AfterDocComp = script;

        }



        #region Preferences
        /// <summary>
        /// 表示倍率を設定する
        /// </summary>
        /// <param name="zoom">Float</param>
        [ExportMethod()]
        [BrowserBusyCheck]
        public void Zoom(float zoom = 1.0F)
        {
            ZoomViewer = browserInstance.GetMarkupDocumentViewer();
            ZoomViewer.SetFullZoomAttribute(zoom);
        }

        /// <summary>
        /// ブラウザのユーザーエージェントを設定する
        /// </summary>
        /// <param name="ua"></param>
        [ExportMethod()]
        public void SetUserAgent(string ua =
                    "Mozilla/5.0 (Linux; U; Android 2.3.5; ja-jp; T-01D Build/F0001) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1")
        {
            Gecko.GeckoPreferences.User["general.useragent.override"] = ua;
        }

        /// <summary>
        /// すべてのCookieを削除する
        /// </summary>
        /// <returns>成功したらtrueを返却する</returns>
        [ExportMethod()]
        [BrowserBusyCheck]
        public bool DeleteAllCookies()
        {
            var ret = false;
            try
            {
                nsICookieManager CookieMan;
                CookieMan = Xpcom.GetService<nsICookieManager>("@mozilla.org/cookiemanager;1");
                CookieMan = Xpcom.QueryInterface<nsICookieManager>(CookieMan);
                CookieMan.RemoveAll();
                ret = true;
            }
            catch(Exception)
            {
                System.Diagnostics.Debug.WriteLine(" Xpcom.DeleteAllCookies(); エラー");
                ret = false;
            }
            return ret;
        }

        #endregion

        #region クリック

        /// <summary>
        /// Xpathで対象を指定し、ヒットした０番目をクリックする。
        /// indexが指定された場合は、それをクリックする。
        /// マイナス指定のときは、すべてクリックする
        /// </summary>
        /// <param name="XPATH"></param>
        /// <param name="index"></param>
        [ExportMethod()]
        public bool ClickXpath(string XPATH , int index = 0)
        {
            bool ret = false;
            //indexがマイナス指定のときは、すべてをクリック
            if (index < 0)
            {
                ret = ClickXpath_All(XPATH);
                return ret;
            }

            try
            {
                GeckoElement[] elements = browserInstance.Document.GetElements(XPATH).ToArray<GeckoElement>();
                GeckoElement target;

                if (elements != null && elements.Length > 0)
                {
                    if (elements.Length > index)
                    {
                        target = elements[index];
                    }
                    else
                    {
                        target = elements[elements.Length - 1];
                    }
                    b_navigatingFired = false;
                    //DOMオブジェクトクリック
                    target.Click();
                    BrowserWait();
                    ret =  true;
                }
            }
            catch
            {
                errmsg = "@@@@@@@@@@@@@@@@@@@@@@@" + Environment.NewLine 
                        + "クリックエラー" + Environment.NewLine
                        + XPATH + Environment.NewLine;
                System.Diagnostics.Debug.WriteLine(errmsg);
                ret = false;
            }
            return ret;
        }

        /// <summary>
        /// Xpathで対象を指定し、ヒットしたすべてをクリックする。
        /// </summary>
        /// <param name="XPATH"></param>
        /// <returns>すべて成功したらtrueを返却する</returns>
        [ExportMethod()]
        private bool ClickXpath_All(string XPATH)
        {
            bool ret = false;
            try
            {
                GeckoElement[] elements = browserInstance.Document.GetElements(XPATH).ToArray<GeckoElement>();

                if(elements != null && elements.Length > 0)
                {
                    foreach (GeckoElement target in elements)
                    {
                        target.Click();
                        BrowserWait();
                    }
                    ret = true;
                }
            }
            catch
            {
                errmsg = "@@@@@@@@@@@@@@@@@@@@@@@" + Environment.NewLine
                        + "クリックエラー ClickXpath_All " + Environment.NewLine
                        + XPATH + Environment.NewLine;
                System.Diagnostics.Debug.WriteLine(errmsg);
                ret = false;
            }
            return ret;

        }

        #endregion

        #region get,set properties
        /// <summary>
        /// 現在のURLを取得する
        /// </summary>
        /// <returns></returns>
        [ExportMethod()]
        public string getURL() { return browserInstance.Url.ToString(); }


        /// <summary>
        /// Xpathで対象を指定し、TextContentを設定する
        /// </summary>
        /// <param name="XPATH"></param>
        /// <returns></returns>
        [ExportMethod()]
        public string[] GetTextContents(string XPATH)
        {
            if(XPATH == null) return null;

            GeckoElement[] temp = browserInstance.Document.GetElements(XPATH).ToArray<GeckoElement>();
            if (temp.Length == 0)
            {  
                return new string[] { "NULL" };
            }

            string[] ret = new string[temp.Length];
            for (int i = 0; i < temp.Length ; i++)
            {
                ret[i] = temp[i].TextContent;
            }
            return ret;
        }

        /// <summary>
        /// GeckoElementのInnerHtmlを取得する
        /// </summary>
        /// <param name="ele"></param>
        /// <returns></returns>
        [ExportMethod()]
        public string GetInnerHtml(GeckoElement ele) { return ele.InnerHtml; }

        /// <summary>
        /// GeckoElementのOuterHtmlを取得する
        /// </summary>
        /// <param name="ele"></param>
        /// <returns></returns>
        [ExportMethod()]
        public string GetOuterHtml(GeckoElement ele) { return ele.OuterHtml; }




        //スクリプト呼び出し対応
        /// <summary>
        /// HTML要素の属性を設定する
        /// </summary>
        /// <param name="ele">GeckoElementまたはXpath</param>
        /// <param name="attName">属性の名前</param>
        /// <param name="attVal">セットする値</param>
        [ExportMethod()]
        [BrowserBusyCheck]
        public void SetAttribute(object ele, string attName, string attVal)
        {
            var GeckoElementobj = ele as GeckoElement;
            if(GeckoElementobj != null)
            {
                SetAttribute(GeckoElementobj, attName, attVal);
                return;
            }
 
            var stringobj = ele as string;
            if(stringobj != null)
            {
                SetAttribute(stringobj, attName, attVal);
                return;
            }
        }

        #region SetAttribute private
        
        /// <summary>
        /// HTML要素の属性を設定する（内部呼び出し用）
        /// </summary>
        /// <param name="ele">GeckoElement</param>
        /// <param name="attName"></param>
        /// <param name="attVal"></param>
        private void SetAttribute(GeckoElement ele, string attName, string attVal)
        {
            try
            {
                ele.SetAttribute(attName, attVal);
            }
            catch(Exception) { }
        }

        /// <summary>
        /// HTML要素の属性を設定する（内部呼び出し用）
        /// </summary>
        /// <param name="ele">XPathString</param>
        /// <param name="attName"></param>
        private void SetAttribute(string Xpath, string attName, string attVal)
        {
            GeckoElement ele;
            try
            {
                ele = browserInstance.Document.GetElements(Xpath).ToArray<GeckoElement>()[0];
                ele.SetAttribute(attName, attVal);
            }
            catch(Exception) { }
        }
        #endregion


        /// <summary>
        /// HTML要素の属性を取得する
        /// </summary>
        /// <param name="ele">GeckoElementまたはXpath
        /// XPath指定の場合は、最初にヒットした要素が対象となる。
        /// </param>
        /// <param name="attName">属性の名前</param>
        /// <returns>属性の値。</returns>
        [ExportMethod()]
        public string GetAttribute(object ele , string attName)
        {
            var GeckoElementobj = ele as GeckoElement;
            if(GeckoElementobj != null)
            {
                return GetAttribute(GeckoElementobj , attName);
            }

            var stringobj = ele as string;
            if(stringobj != null)
            {
                return GetAttribute(stringobj , attName);
            }
            return null;
        }
        #region GetAttribute private
        /// <summary>
        /// HTML要素の属性を取得する（内部呼び出し用）
        /// </summary>
        /// <param name="ele">GeckoElement</param>
        /// <param name="attName"></param>
        /// <returns></returns>
        private string GetAttribute(GeckoElement ele , string attName)
        {
            return  ele.GetAttribute(attName);
        }

        /// <summary>
        /// HTML要素の属性を取得する（内部呼び出し用）
        /// </summary>
        /// <param name="Xpath">XPathString</param>
        /// <param name="attName"></param>
        /// <returns></returns>
        private string GetAttribute(string Xpath, string attName)
        {
            string ret = "";

            GeckoElement ele;
            try
            {
                ele = browserInstance.Document.GetElements(Xpath).ToArray<GeckoElement>()[0];
                ret = ele.GetAttribute(attName);
            }
            catch(Exception) { }

            return ret;
        }
        #endregion

        /// <summary>
        /// Xpathで対象を指定し、TextContentを取得する
        /// </summary>
        /// <param name="Xpath"></param>
        /// <param name="textcontent"></param>
        [ExportMethod()]
        [BrowserBusyCheck]
        public void SetTextcontents(string Xpath , string textcontent)
        {
            GeckoElement ele;
            try
            {
                ele = browserInstance.Document.GetElements(Xpath).ToArray<GeckoElement>()[0];
                ele.TextContent = textcontent;
            }
            catch (Exception) { }
        }



        #endregion



        #region CreateElement,RemoveElement,GetElementById

        /// <summary>
        /// BODYにHTML要素を追加
        /// </summary>
        /// <param name="ele">GeckoElement</param>
        [ExportMethod()][BrowserBusyCheck]
        public void CreateElement(GeckoElement ele)  {  browserInstance.Document.Body.AppendChild(ele); }
        
        /// <summary>
        /// BODYからHTML要素を削除
        /// </summary>
        /// <param name="ele">GeckoElement</param>
        [ExportMethod()][BrowserBusyCheck]
        public void RemoveElement(GeckoElement ele)  { browserInstance.Document.Body.RemoveChild(ele); }

        /// <summary>
        /// IDで指定したHTML要素を取得
        /// </summary>
        /// <param name="id"></param>
        /// <returns>GeckoElement</returns>
        [ExportMethod()]
        public GeckoElement GetElementById(string id)
         {
            GeckoElement ret = null; 
            try 
	        {
                ret = (GeckoElement)browserInstance.Document.GetElementById(id);
	        }
            catch(Exception) { }
            return ret;
         }

        /// <summary>
        /// 指定したHTML要素を取得
        /// </summary>
        /// <param name="name"></param>
        /// <returns>GeckoElement[]</returns>
        [ExportMethod()]
        public GeckoElement[] GetElementsByName(string name)
        {
            GeckoElement[] ret = null;
            try
            {
                ret = (GeckoElement[])browserInstance.Document.GetElementsByName(name).ToArray();
            }
            catch(Exception) {}
            return ret;
        }

        /// <summary>
        /// XPathで指定したHTML要素を取得
        /// </summary>
        /// <param name="XPath"></param>
        /// <returns>GeckoElement[]</returns>
        [ExportMethod()]
        public GeckoElement[] GetElements(string XPath)
        {
            GeckoElement[] ret = null;
            try
            {
                ret = (GeckoElement[])browserInstance.Document.GetElements(XPath).ToArray();
            }
            catch(Exception) { }
            return ret;
        }

        #endregion


        #region Navigate,BrowserWait
        /// <summary>
        /// URL移動
        /// </summary>
        /// <param name="URL">URL</param>
        /// <param name="b_waitNavigateStart">ナビゲート開始を待つ</param>
        /// <param name="timeoutMillisec">タイムアウト　ミリ秒</param>
        [ExportMethod()]
        public void Navigate(string URL, bool b_waitNavigateStart = true, int timeoutMillisec = 30000)
        {
            BrowserWait(false);
            b_navigatingFired = false;
            browserInstance.Navigate(URL);

            if(b_waitNavigateStart)
            {
                //ナビゲート開始まで最大0.5秒まつ
                DateTime t = DateTime.Now;
                TimeSpan ts;
                while(b_navigatingFired == false)
                {
                    System.Threading.Thread.Sleep(50);
                    System.Diagnostics.Debug.Write("=");
                    Application.DoEvents();
                    //↓これないと固まることが。。
                    Application.RaiseIdle(new EventArgs());

                    ts = DateTime.Now - t;
                    if(ts.TotalMilliseconds > 500)
                        break;
                }
            }
            //ブラウザ待ち
            BrowserWait(b_waitNavigateStart ,  timeoutMillisec);
        }

        /// <summary>
        /// ブラウザの読み込み完了を待つ
        /// </summary>
        /// <param name="b_waitNavigateStart">ナビゲート開始を待つ</param>
        /// <param name="timeoutMillisec">タイムアウト　ミリ秒</param>
        [ExportMethod()]
        public void BrowserWait(bool b_waitNavigateStart = true , int timeoutMillisec = 30000)
        {
            DateTime t;
            TimeSpan ts;
            if(b_waitNavigateStart)
            {
                //ナビゲート開始まで最大0.2秒まつ
                t = DateTime.Now;
                while(b_navigatingFired == false)
                {
                    System.Threading.Thread.Sleep(50);
                    System.Diagnostics.Debug.Write("+");
                    Application.DoEvents();
                    //↓これないと固まることが。。
                    Application.RaiseIdle(new EventArgs());

                    ts = DateTime.Now - t;
                    if(ts.TotalMilliseconds > 200)
                        break;
                }
            }

            //ナビゲート発生してないなら、ドキュメント読み込み完了とする
            if(b_navigatingFired == false)
            {
                b_DocCompleted = true;
            }

            t = DateTime.Now;
            while( ( b_DocCompleted == false || browserInstance.IsAjaxBusy || browserInstance.IsBusy ))
            {
                System.Threading.Thread.Sleep(50);
                System.Diagnostics.Debug.Write("-");
                Application.DoEvents();
                //↓これないと固まることが。。
                Application.RaiseIdle(new EventArgs());
                //
                ts = DateTime.Now - t;
                if(ts.TotalMilliseconds > timeoutMillisec)
                    break;
            }
        }

        #endregion

        #region Utilities
        /// <summary>
        /// 指定されたHTML要素のXPathを返却。
        /// Xpathの形式は、HTMLからの絶対パス
        /// </summary>
        /// <param name="ele">GeckoElement</param>
        /// <returns></returns>
        [ExportMethod()]
        public string getXpathFromElement(GeckoElement ele)
        {
            //現在の位置から、さかのぼる
            //同階層にいる同じタグを数えて、自分が何番目の兄弟か得る
            //親がいる場合は、その親をポイントして同じ処理を繰り返す
            //同じタグがなく、自分だけのときは[1]をつけない（XPATHエラーなるので）
            //後ろに兄弟がいるかもチェックする（複数ヒットをさけるため）
            if(ele == null) return "";

            int cntNodes = 0;
            int elepos = 0;
            string xpath ="";
            string targetTagname = ele.TagName.ToLower();

            if (ele.Parent == null)
            {
                return "/" + targetTagname;
            }

            foreach (GeckoNode node in ele.Parent.ChildNodes)
            {
                GeckoElement e = node.PreviousSibling as GeckoElement;
                if (e != null && e.TagName.ToLower() == targetTagname)
                {
                    cntNodes++;
                    if(e.Equals(ele))
                    {
                        elepos = cntNodes;
                    }
                }
            }

            if (elepos > 1)
            {
                xpath = targetTagname + "[" + elepos.ToString() + "]";
            }
            else
            {
                xpath = targetTagname;
            }

            //上へさかのぼる
            if (ele.ParentElement != null)
            {
                GeckoElement e = ele.ParentElement as GeckoElement;
                if (e != null)
                {
                    xpath = getXpathFromElement(e) + "/" + xpath;
                }
            }
            return xpath;
        }//getXpathFromElement

        #endregion


    }

}