# -*- coding: utf-8 -*-
#
# Copyright (c) Django Software Foundation and individual contributors.
# Copyright (c) Eduardo Robles Elvira <edulix AT agoravoting DOT com>
#
# All rights reserved.

# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:

    #1. Redistributions of source code must retain the above copyright notice,
       #this list of conditions and the following disclaimer.

    #2. Redistributions in binary form must reproduce the above copyright
       #notice, this list of conditions and the following disclaimer in the
       #documentation and/or other materials provided with the distribution.

    #3. Neither the name of Django nor the names of its contributors may be used
       #to endorse or promote products derived from this software without
       #specific prior written permission.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import hmac
import hashlib
import time

# Use the system PRNG if possible
import random
try:
    random = random.SystemRandom()
    using_sysrandom = True
except NotImplementedError:
    import warnings
    warnings.warn('A secure pseudo-random number generator is not available '
                  'on your system. Falling back to Mersenne Twister.')
    using_sysrandom = False


from flask import current_app

def force_bytes(s, encoding='utf-8', errors='strict'):
    '''
    Force input to be encoded as bytes.

    If it's a string, encode it. if it's bytes already, return it.
    '''
    if isinstance(s, bytes):
        return s
    else:
        return s.encode(encoding, errors)

def force_str(s, encoding='utf-8', errors='strict'):
    '''
    Force input to be decoded as a string.

    If it's a string, encode it. if it's bytes already, return it.
    '''
    if isinstance(s, bytes):
        return s.decode(encoding, errors)
    else:
        return s

def salted_hmac(key_salt, value, secret=None):
    """
    Returns the HMAC-SHA1 of 'value', using a key generated from key_salt and a
    secret (which defaults to settings.SECRET_KEY).

    A different key_salt should be passed in for every application of HMAC.
    """
    if secret is None:
        secret = current_app.config.get("SECRET_KEY", "")

    # We need to generate a derived key from our base key.  We can do this by
    # passing the key_salt and our base key through a pseudo-random function and
    # SHA1 works nicely.
    key = hashlib.sha1((key_salt + secret).encode('utf-8')).digest()

    # If len(key_salt + secret) > sha_constructor().block_size, the above
    # line is redundant and could be replaced by key = key_salt + secret, since
    # the hmac module does the same thing for keys longer than the block size.
    # However, we need to ensure that we *always* do this.
    return hmac.new(key, msg=force_bytes(value), digestmod=hashlib.sha1)


def get_random_string(length=12,
                      allowed_chars='abcdefghijklmnopqrstuvwxyz'
                                    'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'):
    """
    Returns a securely generated random string.

    The default length of 12 with the a-z, A-Z, 0-9 character set returns
    a 71-bit value. log_2((26+26+10)^12) =~ 71 bits
    """
    secret = current_app.config.get("SECRET_KEY", "")
    if not using_sysrandom:
        # This is ugly, and a hack, but it makes things better than
        # the alternative of predictability. This re-seeds the PRNG
        # using a value that is hard for an attacker to predict, every
        # time a random string is required. This may change the
        # properties of the chosen random sequence slightly, but this
        # is better than absolute predictability.
        random.seed(
            hashlib.sha256(
                ("%s%s%s" % (
                    random.getstate(),
                    time.time(),
                    secret)).encode('utf-8')
                ).digest())
    return ''.join([random.choice(allowed_chars) for i in range(length)])


def constant_time_compare(val1, val2):
    """
    Returns True if the two strings are equal, False otherwise.

    The time taken is independent of the number of characters that match.
    """
    val1 = force_bytes(val1)
    val2 = force_bytes(val2)
    if len(val1) != len(val2):
        return False
    result = 0
    for x, y in zip(val1, val2):
        result |= x ^ y
    return result == 0

def hash_str(s):
    """
    sha-512
    """
    import hashlib

    s_utf8 = s.encode('utf-8')
    m = hashlib.sha512()
    m.update(s_utf8)

    return m.hexdigest()

def hash_token(token):
    """
    sha-512
    """
    import hashlib

    token_utf8 = token.encode('utf-8')
    m = hashlib.sha512()
    m.update(token_utf8)

    return m.hexdigest()
