"""
 Copyright (C) 2010-2013, Ryan Fan

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Library General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""

import httplib, urllib, urllib2
import logging
import gzip
import urlparse
import os

from httplib import BadStatusLine
from StringIO import StringIO


# socket timeout in seconds
DEFAULT_SOCKET_TIMEOUT = 180

bad_headers = range(400, 404) + range(405, 418) + range(500, 506)
class BadHeader(Exception):
    def __init__(self, code, content=""):
        Exception.__init__(self, "Bad server response: %s %s" % (code, httplib.responses.get(int(code), "Unknown Header")))
        self.code = code
        self.content = content

class UrllibHTTPRequest:
    def __init__(self, cookiefile):
        self.cj = None
        self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cj))
        self.opener.addheaders = [
            ('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:15.0) Gecko/20100101 Firefox/15.0.1'),
            ( 'Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
            ('Accept-Language', 'zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3'),
            ('Accept-Encoding', 'gzip, deflate'),
            ('Connection', 'keep-alive')
        ]

        self.lastURL = None
        self.lastEffectiveURL = None
        self.abort = False
        # last http code
        self.code = 0

        self.httpheader = ""
        self.httpbody = ""

    def headFetch(self, url, data = None):
        if data: data = urllib.urlencode(data)
        url = url + '?' + data
        parts = urlparse.urlparse(url)
        if not parts.path:
            path = "/"

        if parts.params:
            path = path + ";" + parts.params

        if parts.query:
            path = path + "?" + parts.query

        conn = httplib.HTTPConnection(parts.netloc, timeout=10)
        conn.request('HEAD',path)
        response = conn.getresponse()
        self.code = self.verifyHeader(response.status)
        self.httpheader = response.getheaders()

        # TODO: raise BadHeader may abort the conn.close
        conn.close()
        return self.httpheader

        #parse header
        #header = {"code": self.code}
        #for line in response.splitlines():
        #    line = line.strip()
        #    if not line or ":" not in line: continue
        #   key, none, value = line.partition(":")
        #   key = key.lower().strip()
        #     value = value.strip()
        #
        #     if key in header:
        #         if type(header[key]) == list:
        #             header[key].append(value)
        #         else:
        #             header[key] = [header[key], value]
        #     else:
        #         header[key] = value
        # res = header



    def fetch(self, url, method="GET", data = None):
        # if need fetch both http header and body
        if data: data = urllib.urlencode(data)

        req = urllib2.Request(url, data)

        method = method.upper()
        if method not in ['POST', 'GET']:
            logging.error("http fetch method is not valid: %s!" % method)
            return None
        else:
            req.get_method = lambda:method

        if data and len(data) > 10:
            data = "..." + data[-10:]

        logging.debug("http method: %s, url: %s, data: %s" % (method, url, data))
        try:
            response = self.opener.open(req, timeout=DEFAULT_SOCKET_TIMEOUT)
        except IOError, e:
            logging.error("We failed to open %s." % url)
            if hasattr(e, 'code'):
                logging.error("Failed with code - %s" % e.code)
            elif hasattr(e, 'reason'):
                logging.error('''Failed to reach a server,
                            This usually means the server doesn't exist or down,
                            or we don't have an internet connection.
                            The reason is %s.''' % e.reason)

            return None
        except BadStatusLine:
            logging.error("Failed to fetch url: %s. because of: BadStatusLine" % url)
            return None
        except Exception, e:
            logging.error("Failed to fetch url: %s. because of: %s" % (url, e))
            return None

        # save server side cookie info
        self.cj.save()

        # handle response
        self.lastEffectiveURL = response.geturl()
        self.httpheader = response.info().headers

        # try to get html content
        keys= map(lambda s:s.upper(), response.info().keys())
        values = map(lambda s:s.upper(), response.info().values())
        if ('CONTENT-ENCODING' in keys) and ('GZIP' in values):
            buf = StringIO(response.read())
            fp = gzip.GzipFile(fileobj=buf)
            self.httpbody = fp.read()
        else:
            self.httpbody = response.read()

        self.code = self.verifyHeader(response.getcode())

        return self.httpbody

    def verifyHeader(self, httpcode):
        """ raise an exceptions on bad headers """
        code = int(httpcode)

        # TODO: raise anyway to be consistent, also rename exception
        if code in bad_headers:
            #404 will NOT raise an exception
            raise BadHeader(code, self.httpbody)

        return code