// Chattr - Twitter client on .NET
//
// Copyright (c) 2007,2008 Katsuhiko Ichinose <ichi@users.sourceforge.jp>
//
// Chattr is Free Software released under GNU General Public License.
//
// $Id: twitte.rb 61 2007-07-17 18:11:25Z ichi $

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
using System.Web;

using System.Diagnostics;
using System.Threading;

namespace Chattr
{
    static class TwitterAPI
    {
        private const string LINGO1 = @"[dfglmntw]";
        private const string LINGO2 = @"(?:direct|follow|get|leave|nudge|whois|track|untrack)";
        private const string JPN = @"[℀-➾　-㏾一-龥豈-鶴！-￬]";
        private const string JPE = @"[℀-➾　-㏾一-龥豈-鶴！-￬0-9a-zA-Z\-_\.!~\*'\(\)/\?&=,:%]";
        private const string LST = @"0-9a-zA-Z\-_\.!~\*'/";
        private const string REGEX_LINGO_11 = "^(" + JPN + "+)(" + LINGO1 + ")(" + JPN + "+)$";
        private const string REGEX_LINGO_12 = "^(" + JPN + "+)(" + LINGO2 + ")(" + JPN + "+)$";
        private const string REGEX_LINGO_21 = "^(" + JPN + "+)(" + LINGO1 + " )(" + JPN + "+)$";
        private const string REGEX_LINGO_22 = "^(" + JPN + "+)(" + LINGO2 + " )(" + JPN + "+)$";
        private const string REGEX_LINGO_31 = "^(" + LINGO1 + ")(" + JPN + "+)$";
        private const string REGEX_LINGO_32 = "^(" + LINGO2 + ")(" + JPN + "+)$";
        private const string REGEX_LINGO_41 = "^(" + LINGO1 + " )(" + JPN + "+)$";
        private const string REGEX_LINGO_42 = "^(" + LINGO2 + " )(" + JPN + "+)$";
        private const string REGEX_URL = "^(.*?)((?:http|https)://" + JPE + "*[" + LST + "])(.*)$";
        private const string API_ROOT = "http://api.twitter.com/1";

        private static SystemException m_Ex = null;
        private static Mutex m_Mutex = new Mutex();

        static public HttpWebResponse GetFollowingTimeline(String access_token, String access_token_secret, String option)
        {
            return GetWithOAuth(API_ROOT + "/statuses/home_timeline.xml" + option, access_token, access_token_secret);
            //return GetWithAuth("http://twitter.com/statuses/friends_timeline.xml" + option, user, pass);
            //return GetWithAuth("http://twitter.com/statuses/friends_timeline.xml?page=1", user, pass);
        }

        static public HttpWebResponse GetUserTimeline(String access_token, String access_token_secret, String option)
        {
            return GetWithOAuth(API_ROOT + "/statuses/user_timeline.xml" + option, access_token, access_token_secret);
            //return GetWithAuth("http://twitter.com/statuses/user_timeline.xml?page=1", user, pass);
        }

        static public HttpWebResponse GetReplies(String access_token, String access_token_secret, String option)
        {
            return GetWithOAuth(API_ROOT + "/statuses/mentions.xml" + option, access_token, access_token_secret);
            //return GetWithAuth(API_ROOT + "/statuses/replies.xml" + option, user, pass);
        }

        static public HttpWebResponse GetDirectMessage(String access_token, String access_token_secret, String option)
        {
            return GetWithOAuth(API_ROOT + "/direct_messages.xml" + option, access_token, access_token_secret);
        }

        static public HttpWebResponse GetProfileImage(String url)
        {
            return Get(url);
        }

        static public HttpWebResponse UserStream(String access_token, String access_token_secret)
        {
            return GetWithOAuth("https://userstream.twitter.com/2/user.json", access_token, access_token_secret);
        }

        static public HttpWebResponse Update(String access_token, String access_token_secret, String status, long replyTo)
        {
            String encode = Encode(status);
            Debug.WriteLine("TwitterAPI.Update: " + encode);
            String data = "status=" + TwitterAPI.UrlEncode(encode, TwitterOAuth.URL_ENCODE);
            if (replyTo != 0)
            {
                data += "&in_reply_to_status_id=" + replyTo.ToString();
            }
            Debug.WriteLine("TwitterAPI.Update: " + data);
            return PostWithOAuth(API_ROOT + "/statuses/update.xml", data, access_token, access_token_secret);
        }

        static public HttpWebResponse Retweet(String access_token, String access_token_secret, long id)
        {
            return PostWithOAuth(API_ROOT + "/statuses/retweet/" + id + ".xml", "", access_token, access_token_secret);
            //return GetWithAuth("http://twitter.com/statuses/retweet/" + id, user, pass);
        }

        static public HttpWebResponse Favorite(String access_token, String access_token_secret, long id)
        {
            return PostWithOAuth(API_ROOT + "/favorites/create/" + id + ".xml", "", access_token, access_token_secret);
            //return GetWithAuth("http://twitter.com/favourings/create/" + id, user, pass);
        }

        static public SystemException GetLastError()
        {
            return m_Ex;
        }

        static public string UrlEncode(string text, string encode)
        {
            Debug.WriteLine("TwitterAPI.UrlEncode: " + text);
            string txt = text;
            string msg = "";
            Regex r = new Regex("^(.*?)("+encode+"+)(.*)$");
            Match m = r.Match(txt);
            while (m.Success)
            {
                msg += m.Groups[1].Value;
                foreach (byte a in Encoding.UTF8.GetBytes(m.Groups[2].Value))
                {
                    msg += string.Format("%{0:X2}", a);
                }
                txt = m.Groups[3].Value;
                m = r.Match(txt);
            }
            msg += txt;
            Debug.WriteLine("TwitterAPI.UrlEncode: " + msg);
            return msg;
        }

        static private string Encode(string text)
        {
            Debug.WriteLine("TwitterAPI.Encode in: " + text);
            string encode = "";
            int n = 0;
            foreach (string s in Regex.Replace(text, "\r", "").Split(new char[] { '\n' }))
            {
                string txt = s;
                if (n == 0)
                {
                    Match m = new Regex(REGEX_LINGO_11, RegexOptions.IgnoreCase).Match(s);
                    if (m.Success)
                    {
                        txt = m.Groups[1].Value + " " + m.Groups[2].Value + m.Groups[3].Value;
                    }
                    else if ((m = new Regex(REGEX_LINGO_12, RegexOptions.IgnoreCase).Match(s)).Success)
                    {
                        txt = m.Groups[1].Value + " " + m.Groups[2].Value + m.Groups[3].Value;
                    }
                    else if ((m = new Regex(REGEX_LINGO_21, RegexOptions.IgnoreCase).Match(s)).Success)
                    {
                        txt = m.Groups[1].Value + " " + m.Groups[2].Value + m.Groups[3].Value;
                    }
                    else if ((m = new Regex(REGEX_LINGO_22, RegexOptions.IgnoreCase).Match(s)).Success)
                    {
                        txt = m.Groups[1].Value + " " + m.Groups[2].Value + m.Groups[3].Value;
                    }
                    else if ((m = new Regex(REGEX_LINGO_31, RegexOptions.IgnoreCase).Match(s)).Success)
                    {
                        txt = " " + m.Groups[1].Value + m.Groups[2].Value;
                    }
                    else if ((m = new Regex(REGEX_LINGO_32, RegexOptions.IgnoreCase).Match(s)).Success)
                    {
                        txt = " " + m.Groups[1].Value + m.Groups[2].Value;
                    }
                    else if ((m = new Regex(REGEX_LINGO_41, RegexOptions.IgnoreCase).Match(s)).Success)
                    {
                        txt = " " + m.Groups[1].Value + m.Groups[2].Value;
                    }
                    else if ((m = new Regex(REGEX_LINGO_42, RegexOptions.IgnoreCase).Match(s)).Success)
                    {
                        txt = " " + m.Groups[1].Value + m.Groups[2].Value;
                    }
                    Debug.WriteLineIf((m = new Regex(REGEX_LINGO_12, RegexOptions.IgnoreCase).Match(txt)).Success, "LINGO_12: " + m.Groups[0]);
                    Debug.WriteLineIf((m = new Regex(LINGO2, RegexOptions.IgnoreCase).Match(txt)).Success, "LINGO2: " + m.Groups[0]);
                }
                string msg = "";
                while (txt != "")
                {
                    Match m = new Regex(REGEX_URL).Match(txt);
                    if (m.Success)
                    {
                        msg += m.Groups[1].Value;
                        txt = m.Groups[3].Value;
                        string m2 = m.Groups[2].Value;
                        if ((m = new Regex(@"^(.*?\?)(.*)$").Match(m2)).Success)
                        {
                            msg += m.Groups[1].Value + UrlEncode(m.Groups[2].Value, @"[^a-zA-Z0-9'\.\-\*\(\)_&=%]");
                        }
                        else
                        {
                            msg += m2;
                        }
                    }
                    else
                    {
                        msg += txt;
                        txt = "";
                    }
                }
                if (n > 0)
                {
                    encode += "\n";
                }
                encode += msg;
                n++;
            }
            Debug.WriteLine("TwitterAPI.Encode out: " + encode);
            return TwitterAPI.UrlEncode(encode, @"[^a-zA-Z0-9'\.\-\*\(\)_&=%]");
        }

        static private void SetProxy(HttpWebRequest req)
        {
            if (Properties.Settings.Default.UseProxy == FormMain.PROXY_CHATTR)
            {
                req.Proxy = FormMain.GetProxy();
            }
        }

        static public HttpWebResponse Get(String url)
        {
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
            req.Method = "GET";
            req.KeepAlive = false;
            req.CookieContainer = null;
            req.Timeout = 30000;
            SetProxy(req);
            HttpWebResponse res = null;
            m_Mutex.WaitOne();
            try
            {
                if (NetworkInterface.GetIsNetworkAvailable())
                {
                    res = (HttpWebResponse)req.GetResponse();
                }
                else
                {
                    throw new WebException("Is not network available.", WebExceptionStatus.ConnectFailure);
                }
            }
            catch (SystemException ex)
            {
                m_Ex = ex;
                Debug.WriteLine("Twitter.Get(\'" + url + "\') Error\n" +
                    ex.Message + "\n" + "Source: " + ex.Source);
                if (ex.GetType() == typeof(WebException))
                {
                    Debug.WriteLine("Status: " + ((WebException)ex).Status);
                }
            }
            finally
            {
                m_Mutex.ReleaseMutex();
            }
            return res;
        }

        static public HttpWebResponse GetWithAuthorization(String url, String authorization)
        {
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
            req.Method = "GET";
            req.KeepAlive = false;
            req.CookieContainer = null;
            req.Timeout = 30000;
            req.Headers.Add(HttpRequestHeader.Authorization, authorization);
            SetProxy(req);
            HttpWebResponse res = null;
            m_Mutex.WaitOne();
            try
            {
                if (NetworkInterface.GetIsNetworkAvailable())
                {
                    res = (HttpWebResponse)req.GetResponse();
                }
                else
                {
                    throw new WebException("Is not network available.", WebExceptionStatus.ConnectFailure);
                }
            }
            catch (SystemException ex)
            {
                m_Ex = ex;
                Debug.WriteLine("Twitter.Get(\'" + url + "\') Error\n" +
                    ex.Message + "\n" + "Source: " + ex.Source);
                if (ex.GetType() == typeof(WebException))
                {
                    Debug.WriteLine("Status: " + ((WebException)ex).Status);
                }
            }
            finally
            {
                m_Mutex.ReleaseMutex();
            }
            return res;
        }

        static public HttpWebResponse PostWithAuthorization(String url, String authorization, String content)
        {
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
            req.Method = "POST";
            req.ContentType = "application/x-www-form-urlencoded";
            req.ContentLength = content.Length;
            req.KeepAlive = false;
            req.CookieContainer = null;
            req.Timeout = 30000;
            req.Headers.Add(HttpRequestHeader.Authorization, authorization);
            SetProxy(req);
            HttpWebResponse res = null;
            m_Mutex.WaitOne();
            try
            {
                if (NetworkInterface.GetIsNetworkAvailable())
                {
                    Stream reqStr = req.GetRequestStream();
                    byte[] data = Encoding.ASCII.GetBytes(content);
                    reqStr.Write(data, 0, data.Length);
                    reqStr.Close();
                    res = (HttpWebResponse)req.GetResponse();
                }
                else
                {
                    throw new WebException("Is not network available.", WebExceptionStatus.ConnectFailure);
                }
            }
            catch (SystemException ex)
            {
                m_Ex = ex;
                Debug.WriteLine("Twitter.Post(\'" + url + "\') Error\n" +
                    ex.Message + "\n" + "Source: " + ex.Source);
                if (ex.GetType() == typeof(WebException))
                {
                    Debug.WriteLine("Status: " + ((WebException)ex).Status);
                }
            }
            finally
            {
                m_Mutex.ReleaseMutex();
            }
            return res;
        }

        static private HttpWebResponse GetWithAuth(String url, String user, String pass)
        {
            Debug.WriteLine("GetWithAuth: " + url);
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
            req.Method = "GET";
            req.KeepAlive = false;
            req.CookieContainer = null;
            req.Timeout = 30000;
            req.Headers.Add(HttpRequestHeader.Authorization, "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(user + ":" + pass)));
            SetProxy(req);
            HttpWebResponse res = null;
            m_Mutex.WaitOne();
            try
            {
                if (NetworkInterface.GetIsNetworkAvailable())
                {
                    res = (HttpWebResponse)req.GetResponse();
                }
                else
                {
                    throw new WebException("Is not network available.", WebExceptionStatus.ConnectFailure);
                }
            }
            catch (SystemException ex)
            {
                m_Ex = ex;
                Debug.WriteLine("Twitter.GetWithAuth(\'" + url + "\') Error\n" +
                    ex.Message + "\n" + "Source: " + ex.Source);
                if (ex.GetType() == typeof(WebException))
                {
                    Debug.WriteLine("Status: " + ((WebException)ex).Status);
                }
            }
            finally
            {
                m_Mutex.ReleaseMutex();
            }
            return res;
        }

        static private HttpWebResponse PostWithAuth(String url, String content, String user, String pass)
        {
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
            req.Method = "POST";
            req.ContentType = "application/x-www-form-urlencoded";
            req.ContentLength = content.Length;
            req.KeepAlive = false;
            req.CookieContainer = null;
            req.Timeout = 30000;
            req.Headers.Add(HttpRequestHeader.Authorization, "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(user + ":" + pass)));
            SetProxy(req);
            HttpWebResponse res = null;
            m_Mutex.WaitOne();
            try
            {
                if (NetworkInterface.GetIsNetworkAvailable())
                {
                    Stream reqStr = req.GetRequestStream();
                    byte[] data = Encoding.ASCII.GetBytes(content);
                    reqStr.Write(data, 0, data.Length);
                    reqStr.Close();
                    res = (HttpWebResponse)req.GetResponse();
                }
                else
                {
                    throw new WebException("Is not network available.", WebExceptionStatus.ConnectFailure);
                }
            }
            catch (SystemException ex)
            {
                m_Ex = ex;
                Debug.WriteLine("Twitter.PostWithAuth(\'" + url + "\') Error\n" +
                    ex.Message + "\n" + "Source: " + ex.Source);
                if (ex.GetType() == typeof(WebException))
                {
                    Debug.WriteLine("Status: " + ((WebException)ex).Status);
                }
            }
            finally
            {
                m_Mutex.ReleaseMutex();
            }
            return res;
        }

        static private HttpWebResponse GetWithOAuth(String url, String access_token, String access_token_secret)
        {
            Debug.WriteLine("GetWithOAuth: " + url);
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
            req.Method = "GET";
            req.KeepAlive = false;
            req.CookieContainer = null;
            req.Timeout = 30000;
            req.Headers.Add(HttpRequestHeader.Authorization, TwitterOAuth.OAuthHeader(req.Method, url, "", access_token, access_token_secret));
            SetProxy(req);
            HttpWebResponse res = null;
            m_Mutex.WaitOne();
            try
            {
                if (NetworkInterface.GetIsNetworkAvailable())
                {
                    res = (HttpWebResponse)req.GetResponse();
                }
                else
                {
                    throw new WebException("Is not network available.", WebExceptionStatus.ConnectFailure);
                }
            }
            catch (SystemException ex)
            {
                m_Ex = ex;
                Debug.WriteLine("Twitter.GetWithOAuth(\'" + url + "\') Error\n" +
                    ex.Message + "\n" + "Source: " + ex.Source);
                if (ex.GetType() == typeof(WebException))
                {
                    Debug.WriteLine("Status: " + ((WebException)ex).Status);
                }
            }
            finally
            {
                m_Mutex.ReleaseMutex();
            }
            return res;
        }

        static private HttpWebResponse PostWithOAuth(String url, String body, String access_token, String access_token_secret)
        {
            Debug.WriteLine("PostWithOAuth: " + url);
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
            req.Method = "POST";
            req.KeepAlive = false;
            req.CookieContainer = null;
            req.Timeout = 30000;
            req.Headers.Add(HttpRequestHeader.Authorization, TwitterOAuth.OAuthHeader(req.Method, url, body, access_token, access_token_secret));
            SetProxy(req);
            HttpWebResponse res = null;
            m_Mutex.WaitOne();
            try
            {
                if (NetworkInterface.GetIsNetworkAvailable())
                {
                    Stream reqStr = req.GetRequestStream();
                    byte[] data = Encoding.ASCII.GetBytes(body);
                    reqStr.Write(data, 0, data.Length);
                    reqStr.Close();
                    res = (HttpWebResponse)req.GetResponse();
                }
                else
                {
                    throw new WebException("Is not network available.", WebExceptionStatus.ConnectFailure);
                }
            }
            catch (SystemException ex)
            {
                m_Ex = ex;
                Debug.WriteLine("Twitter.PostWithOAuth(\'" + url + "\') Error\n" +
                    ex.Message + "\n" + "Source: " + ex.Source);
                if (ex.GetType() == typeof(WebException))
                {
                    Debug.WriteLine("Status: " + ((WebException)ex).Status);
                }
            }
            finally
            {
                m_Mutex.ReleaseMutex();
            }
            return res;
        }
    }
}
