# This file is part of Tryton.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from sql import Query, Expression, Column, Null

from .field import Field, SQLType
from ...transaction import Transaction
from ... import backend
from ...tools import grouped_slice, reduce_ids
from ...filestore import filestore


class Binary(Field):
    '''
    Define a binary field (``bytes``).
    '''
    _type = 'binary'
    cast = bytearray if bytes == str else bytes

    def __init__(self, string='', help='', required=False, readonly=False,
            domain=None, states=None, select=False, on_change=None,
            on_change_with=None, depends=None, context=None, loading='lazy',
            filename=None, file_id=None, store_prefix=None):
        if filename is not None:
            self.filename = filename
            if depends is None:
                depends = [filename]
            else:
                depends.append(filename)
        self.file_id = file_id
        self.store_prefix = store_prefix
        super(Binary, self).__init__(string=string, help=help,
            required=required, readonly=readonly, domain=domain, states=states,
            select=select, on_change=on_change, on_change_with=on_change_with,
            depends=depends, context=context, loading=loading)

    def get(self, ids, model, name, values=None):
        '''
        Convert the binary value into ``bytes``

        :param ids: a list of ids
        :param model: a string with the name of the model
        :param name: a string with the name of the field
        :param values: a dictionary with the read values
        :return: a dictionary with ids as key and values as value
        '''
        if values is None:
            values = {}
        transaction = Transaction()
        res = {}
        converter = self.cast
        default = None
        format_ = Transaction().context.get(
            '%s.%s' % (model.__name__, name), '')
        if format_ == 'size':
            converter = len
            default = 0

        if self.file_id:
            table = model.__table__()
            cursor = transaction.connection.cursor()

            prefix = self.store_prefix
            if prefix is None:
                prefix = transaction.database.name

            if format_ == 'size':
                store_func = filestore.size
            else:
                def store_func(id, prefix):
                    return self.cast(filestore.get(id, prefix=prefix))

            for sub_ids in grouped_slice(ids):
                cursor.execute(*table.select(
                        table.id, Column(table, self.file_id),
                        where=reduce_ids(table.id, sub_ids)
                        & (Column(table, self.file_id) != Null)
                        & (Column(table, self.file_id) != '')))
                for record_id, file_id in cursor:
                    try:
                        res[record_id] = store_func(file_id, prefix)
                    except (IOError, OSError):
                        pass

        for i in values:
            if i['id'] in res:
                continue
            value = i[name]
            if value:
                if isinstance(value, unicode):
                    value = value.encode('utf-8')
                value = converter(value)
            else:
                value = default
            res[i['id']] = value
        for i in ids:
            res.setdefault(i, default)
        return res

    def set(self, Model, name, ids, value, *args):
        transaction = Transaction()
        table = Model.__table__()
        cursor = transaction.connection.cursor()

        prefix = self.store_prefix
        if prefix is None:
            prefix = transaction.database.name

        args = iter((ids, value) + args)
        for ids, value in zip(args, args):
            if self.file_id:
                columns = [Column(table, self.file_id), Column(table, name)]
                values = [
                    filestore.set(value, prefix) if value else None, None]
            else:
                columns = [Column(table, name)]
                values = [self.sql_format(value)]
            cursor.execute(*table.update(columns, values,
                    where=reduce_ids(table.id, ids)))

    @staticmethod
    def sql_format(value):
        if isinstance(value, (Query, Expression)):
            return value
        db_type = backend.name()
        if db_type == 'postgresql' and value is not None:
            import psycopg2
            return psycopg2.Binary(value)
        return value

    def sql_type(self):
        db_type = backend.name()
        if db_type == 'postgresql':
            return SQLType('BYTEA', 'BYTEA')
        elif db_type == 'mysql':
            return SQLType('LONGBLOB', 'LONGBLOB')
        return SQLType('BLOB', 'BLOB')
