#!/usr/bin/python
# -*- coding:utf-8 -*-
# Copyright 2019 Huawei Technologies Co.,Ltd.
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use
# this file except in compliance with the License.  You may obtain a copy of the
# License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software distributed
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations under the License.

import re
import base64
import hashlib
import os
from obs import const
from obs import progress

if const.IS_PYTHON2:
    import urllib
else:
    import urllib.parse as urllib
from obs.ilog import INFO, ERROR

def to_bool(item):
    try:
        return True if item is not None and str(item).lower() == 'true' else False
    except Exception:
        return None

def to_int(item):
    try:
        return int(item)
    except Exception:
        return None

def to_long(item):
    try:
        return const.LONG(item)
    except Exception:
        return None

def to_float(item):
    try:
        return float(item)
    except Exception:
        return None

def to_string(item):
    try:
        return str(item) if item is not None else ''
    except Exception:
        return ''

def is_valid(item):
    return item is not None and item.strip() != ''

class RequestFormat(object):

    @staticmethod
    def get_pathformat():
        return PathFormat()

    @staticmethod
    def get_subdomainformat():
        return SubdomainFormat()

    @classmethod
    def convert_path_string(cls, path_args, allowdNames=None):
        e = ''
        if isinstance(path_args, dict):
            e1 = '?'
            e2 = '&'
            for path_key, path_value in path_args.items():
                flag = True
                if allowdNames is not None and path_key not in allowdNames:
                    flag = False
                if flag:
                    path_key = encode_item(path_key, '/')
                    if path_value is None:
                        e1 += path_key + '&'
                        continue
                    e2 += path_key + '=' + encode_item(path_value, '/') + '&'
            e = (e1 + e2).replace('&&', '&').replace('?&', '?')[:-1]
        return e

    def get_endpoint(self, server, port, bucket):
        return

    def get_pathbase(self, bucket, key):
        return

    def get_url(self, bucket, key, path_args):
        return

class PathFormat(RequestFormat):

    def get_server(self, server, bucket):
        return server

    def get_pathbase(self, bucket, key):
        if bucket:
            return '/' + bucket + '/' + encode_object_key(key) if key else '/' + bucket
        return '/' + encode_object_key(key) if key else '/'    

    def get_endpoint(self, server, port, bucket):
        if port == 80 or port == 443:
            return server
        return server + ':' + str(port)

    def get_url(self, bucket, key, path_args):
        path_base = self.get_pathbase(bucket, key)
        path_arguments = self.convert_path_string(path_args)
        return path_base + path_arguments

    def get_full_url(self, is_secure, server, port, bucket, key, path_args):
        url = 'https://' if is_secure else 'http://'
        url += self.get_endpoint(server, port, bucket)
        url += self.get_url(bucket, key, path_args)
        return url

class SubdomainFormat(RequestFormat):

    def get_server(self, server, bucket):
        return bucket + '.' + server if bucket else server

    def get_pathbase(self, bucket, key):
        if key is None:
            return '/'
        return '/' + encode_object_key(key)

    def get_endpoint(self, server, port, bucket):
        if port == 80 or port == 443:
            return self.get_server(server, bucket)
        return self.get_server(server, bucket) + ':' + str(port)

    def get_url(self, bucket, key, path_args):
        url = self.convert_path_string(path_args)
        return self.get_pathbase(bucket, key) + url

    def get_full_url(self, is_secure, server, port, bucket, key, path_args):
        url = 'https://' if is_secure else 'http://'
        url += self.get_endpoint(server, port, bucket)
        url += self.get_url(bucket, key, path_args)
        return url

class delegate(object):
    def __init__(self, conn):
        self.conn = conn
    
    def send(self, data, final=False, stream_id=None):
        self.conn.send(data)
        
def conn_delegate(conn):
    return delegate(conn)

def get_readable_entity(readable, chunk_size=65536, notifier=None, auto_close=True):
    if notifier is None:
        notifier = progress.NONE_NOTIFIER
    def entity(conn):
        try:
            while True:
                chunk = readable.read(chunk_size)
                if not chunk:
                    conn.send('0\r\n\r\n' if const.IS_PYTHON2 else '0\r\n\r\n'.encode('UTF-8'), final=True)
                    break
                newReadCount = len(chunk)
                if newReadCount > 0:
                    notifier.send(newReadCount)
                hex_chunk = hex(len(chunk))[2:]
                conn.send(hex_chunk if const.IS_PYTHON2 else hex_chunk.encode('UTF-8'))
                conn.send('\r\n' if const.IS_PYTHON2 else '\r\n'.encode('UTF-8'))
                conn.send(chunk)
                conn.send('\r\n' if const.IS_PYTHON2 else '\r\n'.encode('UTF-8'))
        finally:
            if hasattr(readable, 'close') and callable(readable.close) and auto_close:
                readable.close()
    return entity

def get_readable_entity_by_totalcount(readable, totalCount, chunk_size=65536, notifier=None, auto_close=True):
    if notifier is None:
        notifier = progress.NONE_NOTIFIER
    def entity(conn):
        try:
            readCount = 0
            while True:
                readCountOnce = chunk_size if totalCount - readCount >= chunk_size else totalCount - readCount
                chunk = readable.read(readCountOnce)
                newReadCount = len(chunk)
                readCount += newReadCount
                if newReadCount > 0:
                    notifier.send(newReadCount)
                if readCount >= totalCount:
                    conn.send(chunk, final=True)
                    break
                conn.send(chunk)
        finally:
            if hasattr(readable, 'close') and callable(readable.close) and auto_close:
                readable.close()
    return entity

def get_file_entity(file_path, chunk_size=65536, notifier=None):
    if notifier is None:
        notifier = progress.NONE_NOTIFIER
    def entity(conn):
        fileSize = os.path.getsize(file_path)
        readCount = 0
        with open(file_path, 'rb') as f:
            while True:
                chunk = f.read(chunk_size)
                newReadCount = len(chunk)
                if newReadCount > 0:
                    notifier.send(newReadCount)
                readCount += newReadCount
                if readCount >= fileSize:
                    conn.send(chunk, final=True)
                    break
                conn.send(chunk)
    return entity

def get_file_entity_by_totalcount(file_path, totalCount, chunk_size=65536, notifier=None):
    if notifier is None:
        notifier = progress.NONE_NOTIFIER
    def entity(conn):
        readCount = 0
        with open(file_path, 'rb') as f:
            while True:
                readCountOnce = chunk_size if totalCount - readCount >= chunk_size else totalCount - readCount
                chunk = f.read(readCountOnce)
                newReadCount = len(chunk)
                if newReadCount > 0:
                    notifier.send(newReadCount)
                readCount += newReadCount
                if readCount >= totalCount:
                    conn.send(chunk, final=True)
                    break
                conn.send(chunk)
    return entity

def get_file_entity_by_offset_partsize(file_path, offset, partSize, chunk_size=65536, notifier=None):
    if notifier is None:
        notifier = progress.NONE_NOTIFIER
    def entity(conn):
        readCount = 0
        with open(file_path, 'rb') as f:
            f.seek(offset)
            while True:
                readCountOnce = chunk_size if partSize - readCount >= chunk_size else partSize - readCount
                chunk = f.read(readCountOnce)
                newReadCount = len(chunk)
                if newReadCount > 0:
                    notifier.send(newReadCount)
                readCount += newReadCount
                if readCount >= partSize:
                    conn.send(chunk, final=True)
                    break
                conn.send(chunk)
    return entity

def is_ipaddress(item):
    return re.match(const.IPv4_REGEX, item)

def md5_encode(unencoded):
    m = hashlib.md5()
    unencoded = unencoded if const.IS_PYTHON2 else (unencoded.encode('UTF-8') if not isinstance(unencoded, bytes) else unencoded)
    m.update(unencoded)
    return m.digest()

def base64_encode(unencoded):
    unencoded = unencoded if const.IS_PYTHON2 else (unencoded.encode('UTF-8') if not isinstance(unencoded, bytes) else unencoded)
    encodeestr = base64.b64encode(unencoded, altchars=None)
    return encodeestr if const.IS_PYTHON2 else encodeestr.decode('UTF-8')

def encode_object_key(key):
    return encode_item(key, '/~')


def encode_item(item, safe='/'):
    return urllib.quote(to_string(item), safe)

def decode_item(item):
    return urllib.unquote(item)


def safe_trans_to_utf8(item):
    if not const.IS_PYTHON2:
        return item
    if item is not None:
        item = safe_encode(item)
        try:
            return item.decode('GB2312').encode('UTF-8')
        except Exception:
            return item
    return None

def safe_trans_to_gb2312(item):
    if not const.IS_PYTHON2:
        return item
    if item is not None:
        item = safe_encode(item)
        try:
            return item.decode('UTF-8').encode('GB2312')
        except Exception:
            return item
    return None

def safe_decode(item):
    if not const.IS_PYTHON2:
        return item
    if isinstance(item, str):
        try:
            item = item.decode('UTF-8')
        except:
            try:
                item = item.decode('GB2312')
            except Exception:
                item = None
    return item

def safe_encode(item):
    if not const.IS_PYTHON2:
        return item
    if isinstance(item, const.UNICODE):
        try:
            item = item.encode('UTF-8')
        except UnicodeDecodeError:
            try:
                item = item.encode('GB2312')
            except Exception:
                item = None
    return item

def md5_file_encode_by_size_offset(file_path=None, size=None, offset=None, chuckSize=None):
    if file_path is not None and size is not None and offset is not None:
        m = hashlib.md5()
        with open(file_path, 'rb') as fp:
            CHUNKSIZE = 65536 if chuckSize is None else chuckSize
            fp.seek(offset)
            read_count = 0
            while read_count < size:
                read_size = CHUNKSIZE if size - read_count >= CHUNKSIZE else size - read_count
                data = fp.read(read_size)
                read_count_once = len(data)
                if read_count_once <= 0:
                    break
                m.update(data)
                read_count += read_count_once
        return m.digest()


def do_close(result, conn, connHolder, log_client=None):
    if not result:
        close_conn(conn, log_client)
    elif result.getheader('connection', '').lower() == 'close':
        if log_client:
            log_client.log(INFO, 'server inform to close connection')
        close_conn(conn, log_client)
    elif to_int(result.status) >= 500 or connHolder is None:
        close_conn(conn, log_client)
    elif hasattr(conn, '_clear') and conn._clear:
        close_conn(conn, log_client)
    else:
        if connHolder is not None:
            try:
                connHolder['connSet'].put_nowait(conn)
            except:
                close_conn(conn, log_client)
        

def close_conn(conn, log_client=None):
    try:
        if conn:
            conn.close()
    except Exception as ex:
        if log_client:
            log_client.log(ERROR, ex)

SKIP_VERIFY_ATTR_TYPE = False
def verify_attr_type(value, allowedAttrType):
    if SKIP_VERIFY_ATTR_TYPE:
        return True
    if isinstance(allowedAttrType, list):
        for t in allowedAttrType:
            if isinstance(value, t):
                return True
        return False
    return isinstance(value, allowedAttrType)

def lazyCallback(*args, **kwargs):
    pass
