# -*- coding: utf-8 -*-
# License: Simplified BSD, 2014
# See LICENSE.txt for more information
from __future__ import absolute_import, print_function, division, unicode_literals


import pytest
import numpy as np
from numpy.testing import assert_allclose, assert_array_equal

from ..parse import toarray, NULLS
from . import sdb, TestBase, teardown_function


class TestScalar(TestBase):


    def check(self, array, expected):
        result = toarray(array)
        expected = np.asarray(expected)
        try:
            expected = expected.astype(result.dtype)
        except ValueError:
            pass

        try:
            assert_allclose(result, expected)
        except TypeError:
            assert_array_equal(result, expected)


    def scalar(self, typ, value, null):
        # build a single-element array
        if not null:
            typ = typ + ' NOT NULL'
        return sdb.afl.build('<x: %s>[i=0:0,10,0]' % typ, value)


    @pytest.mark.parametrize(
        ('typ', 'value', 'expected'),

        ## Numeric and Boolean Sclars
        [(typ, value, None)
         for (typ, values) in [
                 # FLT_MIN = 1.175494e-38, FLT_MAX = 3.402823e+38
                 ('float',  (-1.0, 0.0, 1.0, 3e35)),
                 ('double', (-1, 0, 1, 1e100, 1e900)),
                 ('int8',   (0, 17, -5)),
                 ('uint8',  (0, 17, 255)),
                 ('int16',  (0, 17, 300, -5)),
                 ('uint16', (0, 17, 300, 2 * 16 - 1)),
                 ('int32',  (0, 256, -2 ** 29)),
                 ('uint32', (0, 256, 2 ** 32 - 1)),
                 ('int64',  (0, 2 ** 35, -300)),
                 ('uint64', (0, 2 ** 63 - 1)),
                 ('bool',   (True, False))]
         for value in values] +

        ## DateTime Scalar
        [('datetime', "'{}'".format(value), [np.datetime64(value + '+0000')])
         for value in ['1970-01-02 00:00:00',
                       '1950-01-01 00:00:00',
                       '2520-12-31 11:59:59']] +

        ## DateTimeTZ Scalar
        [pytest.mark.skip('SciDB 16.9 issue https://github.com/Paradigm4/SciDB/issues/9')(
            ('datetimetz', "'{}'".format(value), [np.datetime64(expect)]))
         for value, expect in [
                 ('1970-01-02 00:00:00 +00:30', '1970-01-02 00:00:00+0030'),
                 ('1970-01-02 00:00:00 -01:30', '1970-01-02 00:00:00-0130')]] +

        ## Char Scalar
        [('char', "'{}'".format(char), [char])
         for char in 'abAB12_ '] +

        ## String Scalar
        [('string', "'{}'".format(value), [value])
         for val in ['a', 'hi', 'hi ho', 'å∫√∂']])
    @pytest.mark.parametrize('null', [False, True])
    def test_scalar(self, typ, value, null, expected):
        if expected is None:
            expected = [value]
        self.check(self.scalar(typ, value, null), expected)


    @pytest.mark.parametrize(
        'typ',
        ['float',
         'double',
         'int8',
         'uint8',
         'int16',
         'uint16',
         'int32',
         'uint32',
         'int64',
         'uint64',
         'bool',
         'datetime',
         pytest.mark.skip('SciDB 16.9 issue https://github.com/Paradigm4/SciDB/issues/9')(
             'datetimetz'),
         'char',
         'string'])
    def test_null(self, typ):
        x = sdb.afl.build('<x: %s>[i=0:0,10,0]' % typ, 'null')
        self.check(x, [NULLS[typ]])


    def test_parse_std(self):
        # regression test
        x = sdb.ones(5).std().toarray()
        assert x == 0


class TestMultiAttributeArrays(object):

    def teardown_method(self, method):
        sdb.reap()

    def test_composite_numbers(self):
        x = sdb.afl.join(sdb.afl.build('<x:float>[i=0:3,10,0]', 'i'),
                         sdb.afl.build('<y:uint8>[i=0:3,10,0]', '2*i'))
        y = toarray(x)

        rng = np.arange(4)

        assert_allclose(y['x'], rng)
        assert_allclose(y['y'], rng * 2)

    def test_numbers_with_nulls(self):
        x = sdb.afl.join(sdb.afl.build('<x:float>[i=0:3,10,0]', 'iif(i>0, i, null)'),
                         sdb.afl.build('<y:uint8 NOT NULL>[i=0:3,10,0]', '2*i'))
        y = toarray(x)
        assert_allclose(y['x'], [np.nan, 1., 2., 3.])
        assert_array_equal(y['y'], [0, 2, 4, 6])

    def test_numbers_string(self):
        x = sdb.afl.join(sdb.afl.build('<x:float NOT NULL>[i=0:3,10,0]', 'i'),
                         sdb.afl.build('<y:string NOT NULL>[i=0:3,10,0]', "'abc'"))

        y = toarray(x)
        assert_allclose(y['x'], [0, 1, 2, 3])
        assert_array_equal(y['y'], ['abc', 'abc', 'abc', 'abc'])

    def test_double_string(self):
        x = sdb.afl.join(sdb.afl.build('<x:string NOT NULL>[i=0:3,10,0]', "'aaa'"),
                         sdb.afl.build('<y:string>[i=0:3,10,0]', "iif(i>0, 'b', null)"))
        y = toarray(x)
        assert_array_equal(y['x'], ['aaa', 'aaa', 'aaa', 'aaa'])
        assert_array_equal(y['y'], [None, 'b', 'b', 'b'])


def test_array():

    a = sdb.afl.build('<a: int32 NOT NULL>[i=0:2,10,0]', 'i*5')
    b = sdb.afl.build('<b: string NOT NULL>[i=0:2,10,0]', "'b'")
    c = sdb.afl.build('<c: float>[i=0:2,10,0]', 'iif(i>0, i/2.0, null)')
    d = sdb.join(a, b, c)

    result = toarray(d)
    assert_array_equal(result['a'], [0, 5, 10])
    assert_array_equal(result['b'], ['b', 'b', 'b'])
    assert_allclose(result['c'], [np.nan, 0.5, 1.0])


def test_nonzero_origin():

    a = sdb.afl.build('<a:int8 NOT NULL>[i=1:5,10,0]', 'i')
    assert_array_equal(toarray(a), [1, 2, 3, 4, 5])


def test_unbounded():

    a = sdb.afl.build('<a:int8 NOT NULL>[i=0:4,10,0]', 'i')
    a = a.redimension('<a:int8 NOT NULL>[i=0:*,10,0]')
    assert_array_equal(toarray(a), [0, 1, 2, 3, 4])
    assert toarray(a).dtype == np.int8


def test_sparse():
    x = sdb.afl.build('<a:int8 NOT NULL>[i=0:1,10,0]', 10)
    x = x.redimension('<a:int8 NOT NULL>[i=0:2,10,0]')
    assert_array_equal(toarray(x), [10, 10, 0])


def test_compression():

    x = sdb.zeros(5, dtype=int)
    y = toarray(x, compression=9)
    assert_array_equal(y, [0, 0, 0, 0, 0])


def test_auto_compression_options():
    sdb.default_compression = None

    x = sdb.zeros(5, dtype=int)

    exp = [0, 0, 0, 0, 0]
    assert_array_equal(toarray(x, compression=None), exp)
    assert_array_equal(toarray(x, compression='auto'), exp)
    sdb.default_compression = 1
    assert_array_equal(toarray(x, compression='auto'), exp)
    assert_array_equal(toarray(x), exp)
    assert_array_equal(toarray(x, compression=9), exp)
    sdb.default_compression = None


def test_dense():
    x = sdb.afl.build('<a:int8 NOT NULL>[i=0:100,7,3, j=0:100,10,2]', 'i+j')

    assert_array_equal(x.toarray(method='sparse'), x.toarray(method='dense'))


def test_fromarray_chunksize():
    from . import unfuzzed
    from_array = unfuzzed['from_array']

    def check(chunk):
        x = np.arange(24).reshape(2, 3, 4)
        y = from_array(x, chunk_size=chunk)
        assert_array_equal(x, y.toarray())
        assert chunk == tuple(y.datashape.chunk_size)

    for chunk in [(2, 3, 4), (1, 2, 2), (1, 1, 1), (5, 5, 5), (5, 1, 5)]:
        yield check, chunk
