import binascii

import whatap.agent.conf.configure as config
from whatap.io.data_outputx import DataOutputX
import whatap.util.lib.pyaes as pyaes
import whatap.util.logging_util as logging_util
from StringIO import StringIO
from itertools import cycle

_libcrypto_import_err = None
try:
    from whatap.util import cypher_libcrypto as _libcrypto_mod
    _HAS_LIBCRYPTO = _libcrypto_mod.is_available()
except Exception as _e:
    import traceback as _tb
    _libcrypto_mod = None
    _HAS_LIBCRYPTO = False
    _libcrypto_import_err = "%s: %s\n%s" % (type(_e).__name__, _e, _tb.format_exc())


def NewCypher(cypher_key , xor_key ):
    cph = Cypher(cypher_key, xor_key)

    return cph

class Cypher:
    def __init__(self, cypher_key, xor_key):
        self.block = int(config.GetConfig().CypherLevel) / 8
        self.cypher_key = cypher_key[:self.block]
        self.xor_key = bytearray(xor_key)
        self.xor_len = len(self.xor_key)
        self._xor_key_bytes = bytes(self.xor_key) if self.xor_len > 0 else b''
        self.cypher = pyaes.AESModeOfOperationECB(self.cypher_key)

        self._fast_aes = None
        if self.block in (16, 24, 32) and _HAS_LIBCRYPTO:
            try:
                self._fast_aes = _libcrypto_mod.LibcryptoAES_ECB(
                    bytes(self.cypher_key))
            except Exception as e:
                logging_util.error(
                    "[Cypher] libcrypto init failed, falling back to pyaes: %s" % e)
                self._fast_aes = None

        if self._fast_aes is not None:
            logging_util.info(
                "[Cypher] AES backend = ctypes+libcrypto (%s)" %
                (_libcrypto_mod.get_library_path() or "?"))
        else:
            if _libcrypto_mod is None:
                err = "import failed: %s" % (_libcrypto_import_err or "unknown")
            else:
                err = "lib load failed: %s" % _libcrypto_mod.get_load_error()
            logging_util.info(
                "[Cypher] AES backend = pyaes (pure python, block=%s, libcrypto_avail=%s)" %
                (self.block, _HAS_LIBCRYPTO))
            logging_util.info("[Cypher] libcrypto unavailable detail: %s" % err)

    def Encrypt(self, data_):
        if self.block == 0 or data_ is None:
            return data_

        dataLen = len(data_)
        remainder = dataLen % self.block
        if remainder != 0:
            data_ += '\0' * (self.block - remainder)

        if self._fast_aes is not None:
            return self._fast_aes.encrypt(data_)

        encrypted_blocks = [
            self.cypher.encrypt(data_[i:i+self.block])
            for i in range(0, len(data_), self.block)
        ]

        return ''.join(encrypted_blocks)

    def Decrypt(self, data_):
        if self.block == 0 or self.cypher is None:
            return data_

        if self._fast_aes is not None:
            return self._fast_aes.decrypt(data_)

        decrypted_blocks = [
            self.cypher.decrypt(data_[i:i+self.block])
            for i in range(0, len(data_), self.block)
        ]
        return ''.join(decrypted_blocks)

    def padding(self, src ):
        if len(src) == self.block:
            return src

        dest = ''+ self.block
        if len(src) > self.block:
            dest= src[:self.block]
        else:
            dest=  src

        return dest

    def Hide(self, data_):
        if self.block == 0 or self.xor_key is None:
            return data_

        data_bytes = bytearray(data_)
        for i in range(len(data_bytes)):
            data_bytes[i] ^= self.xor_key[i % self.xor_len]

        return str(data_bytes)

    def HideLong(self, data_):
        if self.block == 0 or self.xor_key is None or self.xor_len == 0:
            return data_

        n = len(data_)
        if n == 0:
            return data_

        key = self._xor_key_bytes
        full_key = (key * (n // self.xor_len + 1))[:n]
        return binascii.unhexlify('%0*x' % (
            n * 2,
            long(binascii.hexlify(bytes(data_)), 16)
            ^ long(binascii.hexlify(full_key), 16)
        ))


def test():
    c = Cypher('0123456789123456', 0)
    encoded = c.Encrypt('hello world')

    o = StringIO()
    for c in encoded:
        o.write('%02x' % ord(c))
    print(encoded, o.getvalue())


if __name__ == '__main__':
    test()
