# Copyright (C) 2014-2015 LiuLang <gsushzhsosgsu@gmail.com>
# Use of this source code is governed by GPLv3 license that can be found
# in http://www.gnu.org/licenses/gpl-3.0.html

import gzip
import http
import http.client
import mimetypes
import os
import traceback
import urllib.parse
import urllib.request
import zlib

from bcloud.RequestCookie import RequestCookie

from bcloud import const
from bcloud.log import logger

RETRIES = 3
TIMEOUT = 50

default_headers = {
    'User-agent': const.USER_AGENT,
    'Referer': const.PAN_REFERER,
    'Host': 'pan.baidu.com',
    # 'x-requested-with': 'XMLHttpRequest',
    'Accept': const.ACCEPT_JSON,
    'Accept-language': 'zh-cn, zh;q=0.5',
    'Accept-encoding': 'gzip, deflate',
    'Pragma': 'no-cache',
    'Cache-control': 'no-cache',
}


def urloption(url, headers={}, retries=RETRIES):
    '''发送OPTION 请求'''
    headers_merged = default_headers.copy()
    for key in headers.keys():
        headers_merged[key] = headers[key]
    schema = urllib.parse.urlparse(url)
    for i in range(retries):
        try:
            conn = http.client.HTTPConnection(schema.netloc)
            conn.request('OPTIONS', url, headers=headers_merged)
            resp = conn.getresponse()
            return resp
        except OSError:
            logger.error(traceback.format_exc())
            # return None
        except:
            logger.error(traceback.format_exc())
            # return None
    return None


class ForbiddenHandler(urllib.request.HTTPErrorProcessor):
    def http_error_403(self, req, fp, code, msg, headers):
        return fp

    http_error_400 = http_error_403
    http_error_500 = http_error_403


def urlopen_simple(url, retries=RETRIES, timeout=TIMEOUT):
    for i in range(retries):
        try:
            return urllib.request.urlopen(url, timeout=timeout)
        except OSError:
            logger.error(traceback.format_exc())

        except:
            logger.error(traceback.format_exc())

    return None


class myRedirctHandler(urllib.request.HTTPRedirectHandler):
    '''
    该处理保证 302 跳转不丢失cookies
    '''
    def __init__(self, cookies=RequestCookie()):
        self.cookies = cookies

    def http_error_302(self, req, fp, code, msg, headers):
        cookie = headers.get_all('Set-Cookie')
        self.cookies.load_list(cookie)

        return super().http_error_302(req, fp, code, msg, headers)

    def redirect_request(self, req, fp, code, msg, headers, newurl):
        if self.cookies is not None:
            req.add_header('Cookie', self.cookies.header_output())
        return super().redirect_request(req, fp, code, msg, headers, newurl)


def urlopen(url, headers={}, data=None, retries=RETRIES, timeout=TIMEOUT, cookie=RequestCookie()):
    '''打开一个http连接, 并返回Request.

    headers 是一个dict. 默认提供了一些项目, 比如User-Agent, Referer等, 就
    不需要重复加入了.

    这个函数只能用于http请求, 不可以用于下载大文件.
    如果服务器支持gzip压缩的话, 就会使用gzip对数据进行压缩, 然后在本地自动
    解压.
    req.data 里面放着的是最终的http数据内容, 通常都是UTF-8编码的文本.
    '''
    headers_merged = default_headers.copy()
    for key in headers.keys():
        headers_merged[key] = headers[key]
    redirectHandler = myRedirctHandler(cookie)
    opener = urllib.request.build_opener(ForbiddenHandler, redirectHandler)
    opener.addheaders = [(k, v) for k, v in headers_merged.items()]

    for i in range(retries):
        try:
            req = opener.open(url, data=data, timeout=timeout)
            encoding = req.headers.get('Content-encoding')
            req.data = req.read()
            if encoding == 'gzip':
                req.data = gzip.decompress(req.data)
            elif encoding == 'deflate':
                req.data = zlib.decompress(req.data, -zlib.MAX_WBITS)
            return req
        except OSError as e:
            logger.error(traceback.format_exc())

        except:
            logger.error(traceback.format_exc())

    return None


def urlopen_without_redirect(url, headers={}, data=None, retries=RETRIES):
    '''请求一个URL, 并返回一个Response对象. 不处理重定向.

    使用这个函数可以返回URL重定向(Error 301/302)后的地址, 也可以重到URL中请
    求的文件的大小, 或者Header中的其它认证信息.
    '''
    headers_merged = default_headers.copy()
    for key in headers.keys():
        headers_merged[key] = headers[key]

    parse_result = urllib.parse.urlparse(url)
    for i in range(retries):
        try:
            conn = http.client.HTTPConnection(parse_result.netloc)
            if data:
                conn.request('POST', url, body=data, headers=headers_merged)
            else:
                conn.request('GET', url, body=data, headers=headers_merged)
            return conn.getresponse()
        except OSError:
            logger.error(traceback.format_exc())
        except:
            logger.error(traceback.format_exc())
            # return None
    return None


def post_multipart(url, headers, fields, files, retries=RETRIES):
    content_type, body = encode_multipart_formdata(fields, files)
    schema = urllib.parse.urlparse(url)

    headers_merged = default_headers.copy()
    for key in headers.keys():
        headers_merged[key] = headers[key]
    headers_merged['Content-Type'] = content_type
    headers_merged['Content-length'] = str(len(body))

    for i in range(retries):
        try:
            h = http.client.HTTPConnection(schema.netloc)
            h.request('POST', url, body=body, headers=headers_merged)
            req = h.getresponse()
            encoding = req.getheader('Content-encoding')
            req.data = req.read()
            if encoding == 'gzip':
                req.data = gzip.decompress(req.data)
            elif encoding == 'deflate':
                req.data = zlib.decompress(req.data, -zlib.MAX_WBITS)
            return req
        except OSError:
            logger.error(traceback.format_exc())
        except:
            logger.error(traceback.format_exc())
            # return None
    return None


def encode_multipart_formdata(fields, files):
    BOUNDARY = b'----------ThIs_Is_tHe_bouNdaRY_$'
    S_BOUNDARY = b'--' + BOUNDARY
    E_BOUNARY = S_BOUNDARY + b'--'
    CRLF = b'\r\n'
    BLANK = b''
    l = []
    for (key, value) in fields:
        l.append(S_BOUNDARY)
        l.append('Content-Disposition: form-data; name="{0}"'.format(
            key).encode())
        l.append(BLANK)
        l.append(value.encode())
    for (key, filename, content) in files:
        l.append(S_BOUNDARY)
        l.append(
            'Content-Disposition: form-data; name="{0}"; filename="{1}"'.format(
                key, filename).encode())
        l.append(BLANK)
        l.append(content)
    l.append(E_BOUNARY)
    l.append(BLANK)
    body = CRLF.join(l)
    content_type = 'multipart/form-data; boundary={0}'.format(BOUNDARY.decode())
    return content_type, body


def get_content_type(filename):
    return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
