# -*- coding: utf-8 -*-

from __future__ import print_function
import sys

import numpy

import tables
from tables import (
    Col, StringCol, Atom, StringAtom, Int16Atom, Int32Atom,
    FloatAtom, Float64Atom,
)
from tables.tests import common
from tables.tests.common import unittest
from tables.tests.common import PyTablesTestCase as TestCase


# Test Record class
class Record(tables.IsDescription):
    var1 = StringCol(itemsize=4)  # 4-character String
    var2 = Col.from_kind('int')   # integer
    var3 = Col.from_kind('int', itemsize=2)  # short integer
    var4 = Col.from_kind('float')  # double (double-precision)
    var5 = Col.from_kind('float', itemsize=4)  # float  (single-precision)
    var6 = Col.from_kind('complex')  # double-precision
    var7 = Col.from_kind('complex', itemsize=8)  # single-precision
    if hasattr(tables, "Float16Atom"):
        var8 = Col.from_kind('float', itemsize=2)  # half-precision
    if hasattr(tables, "Float96Atom"):
        var9 = Col.from_kind('float', itemsize=12)  # extended-precision
    if hasattr(tables, "Float128Atom"):
        var10 = Col.from_kind('float', itemsize=16)  # extended-precision
    if hasattr(tables, "Complex192Atom"):
        var11 = Col.from_kind('complex', itemsize=24)  # extended-precision
    if hasattr(tables, "Complex256Atom"):
        var12 = Col.from_kind('complex', itemsize=32)  # extended-precision


class RangeTestCase(common.TempFileMixin, TestCase):
    title = "This is the table title"
    expectedrows = 100
    maxshort = 2 ** 15
    maxint = 2147483648   # (2 ** 31)
    compress = 0

    def setUp(self):
        super(RangeTestCase, self).setUp()
        self.rootgroup = self.h5file.root

        # Create a table
        self.table = self.h5file.create_table(self.rootgroup, 'table',
                                              Record, self.title)

    def test00_range(self):
        """Testing the range check."""

        rec = self.table.row

        # Save a record
        i = self.maxshort
        rec['var1'] = '%04d' % (i)
        rec['var2'] = i
        rec['var3'] = i
        rec['var4'] = float(i)
        rec['var5'] = float(i)
        rec['var6'] = float(i)
        rec['var7'] = complex(i, i)
        if hasattr(tables, "Float16Atom"):
            rec['var8'] = float(i)
        if hasattr(tables, "Float96Atom"):
            rec['var9'] = float(i)
        if hasattr(tables, "Float128Atom"):
            rec['var10'] = float(i)
        try:
            rec.append()
        except ValueError:
            if common.verbose:
                (type, value, traceback) = sys.exc_info()
                print("\nGreat!, the next ValueError was catched!")
                print(value)
            pass
        else:
            if common.verbose:
                print(
                    "\nNow, the range overflow no longer issues a ValueError")

    def test01_type(self):
        """Testing the type check."""

        rec = self.table.row
        # Save a record
        i = self.maxshort
        rec['var1'] = '%04d' % (i)
        rec['var2'] = i
        rec['var3'] = i % self.maxshort
        rec['var5'] = float(i)

        with self.assertRaises(TypeError):
            rec['var4'] = "124c"

        rec['var6'] = float(i)
        rec['var7'] = complex(i, i)
        if hasattr(tables, "Float16Atom"):
            rec['var8'] = float(i)
        if hasattr(tables, "Float96Atom"):
            rec['var9'] = float(i)
        if hasattr(tables, "Float128Atom"):
            rec['var10'] = float(i)


# Check the dtype read-only attribute
class DtypeTestCase(common.TempFileMixin, TestCase):

    def test00a_table(self):
        """Check dtype accessor for Table objects."""

        a = self.h5file.create_table('/', 'table', Record)
        self.assertEqual(a.dtype, a.description._v_dtype)

    def test00b_column(self):
        """Check dtype accessor for Column objects."""

        a = self.h5file.create_table('/', 'table', Record)
        c = a.cols.var3
        self.assertEqual(c.dtype, a.description._v_dtype['var3'])

    def test01_array(self):
        """Check dtype accessor for Array objects."""

        a = self.h5file.create_array('/', 'array', [1, 2])
        self.assertEqual(a.dtype, a.atom.dtype)

    def test02_carray(self):
        """Check dtype accessor for CArray objects."""

        a = self.h5file.create_carray('/', 'array', atom=FloatAtom(),
                                      shape=[1, 2])
        self.assertEqual(a.dtype, a.atom.dtype)

    def test03_carray(self):
        """Check dtype accessor for EArray objects."""

        a = self.h5file.create_earray('/', 'array', atom=FloatAtom(),
                                      shape=[0, 2])
        self.assertEqual(a.dtype, a.atom.dtype)

    def test04_vlarray(self):
        """Check dtype accessor for VLArray objects."""

        a = self.h5file.create_vlarray('/', 'array', FloatAtom())
        self.assertEqual(a.dtype, a.atom.dtype)


class ReadFloatTestCase(common.TestFileMixin, TestCase):
    h5fname = TestCase._testFilename("float.h5")
    nrows = 5
    ncols = 6

    def setUp(self):
        super(ReadFloatTestCase, self).setUp()
        x = numpy.arange(self.ncols)
        y = numpy.arange(self.nrows)
        y.shape = (self.nrows, 1)
        self.values = x + y

    def test01_read_float16(self):
        dtype = "float16"
        if hasattr(numpy, dtype):
            ds = getattr(self.h5file.root, dtype)
            self.assertFalse(isinstance(ds, tables.UnImplemented))
            self.assertEqual(ds.shape, (self.nrows, self.ncols))
            self.assertEqual(ds.dtype, dtype)
            self.assertTrue(common.allequal(
                ds.read(), self.values.astype(dtype)))
        else:
            with self.assertWarns(UserWarning):
                ds = getattr(self.h5file.root, dtype)
            self.assertTrue(isinstance(ds, tables.UnImplemented))

    def test02_read_float32(self):
        dtype = "float32"
        ds = getattr(self.h5file.root, dtype)
        self.assertFalse(isinstance(ds, tables.UnImplemented))
        self.assertEqual(ds.shape, (self.nrows, self.ncols))
        self.assertEqual(ds.dtype, dtype)
        self.assertTrue(common.allequal(
            ds.read(), self.values.astype(dtype)))

    def test03_read_float64(self):
        dtype = "float64"
        ds = getattr(self.h5file.root, dtype)
        self.assertFalse(isinstance(ds, tables.UnImplemented))
        self.assertEqual(ds.shape, (self.nrows, self.ncols))
        self.assertEqual(ds.dtype, dtype)
        self.assertTrue(common.allequal(
            ds.read(), self.values.astype(dtype)))

    def test04_read_longdouble(self):
        dtype = "longdouble"
        if hasattr(tables, "Float96Atom") or hasattr(tables, "Float128Atom"):
            ds = getattr(self.h5file.root, dtype)
            self.assertFalse(isinstance(ds, tables.UnImplemented))
            self.assertEqual(ds.shape, (self.nrows, self.ncols))
            self.assertEqual(ds.dtype, dtype)
            self.assertTrue(common.allequal(
                ds.read(), self.values.astype(dtype)))

            if hasattr(tables, "Float96Atom"):
                self.assertEqual(ds.dtype, "float96")
            elif hasattr(tables, "Float128Atom"):
                self.assertEqual(ds.dtype, "float128")
        else:
            # XXX: check
            # the behavior depends on the HDF5 lib configuration
            try:
                with self.assertWarns(UserWarning):
                    ds = getattr(self.h5file.root, dtype)
                self.assertTrue(isinstance(ds, tables.UnImplemented))
            except AssertionError:
                from tables.utilsextension import _broken_hdf5_long_double
                if not _broken_hdf5_long_double():
                    ds = getattr(self.h5file.root, dtype)
                    self.assertEqual(ds.dtype, "float64")

    def test05_read_quadprecision_float(self):
        # XXX: check
        try:
            with self.assertWarns(UserWarning):
                ds = self.h5file.root.quadprecision
            self.assertTrue(isinstance(ds, tables.UnImplemented))
        except AssertionError:
            # NOTE: it would be nice to have some sort of message that warns
            #       against the potential precision loss: the quad-precision
            #       dataset actually uses 128 bits for each element, not just
            #       80 bits (longdouble)
            ds = self.h5file.root.quadprecision
            self.assertEqual(ds.dtype, "longdouble")


class AtomTestCase(TestCase):
    def test_init_parameters_01(self):
        atom1 = StringAtom(itemsize=12)
        atom2 = atom1.copy()
        self.assertEqual(atom1, atom2)
        self.assertEqual(str(atom1), str(atom2))
        self.assertFalse(atom1 is atom2)

    def test_init_parameters_02(self):
        atom1 = StringAtom(itemsize=12)
        atom2 = atom1.copy(itemsize=100, shape=(2, 2))
        self.assertEqual(atom2,
                         StringAtom(itemsize=100, shape=(2, 2), dflt=b''))

    def test_init_parameters_03(self):
        atom1 = StringAtom(itemsize=12)
        self.assertRaises(TypeError, atom1.copy, foobar=42)

    def test_from_dtype_01(self):
        atom1 = Atom.from_dtype(numpy.dtype((numpy.int16, (2, 2))))
        atom2 = Int16Atom(shape=(2, 2), dflt=0)
        self.assertEqual(atom1, atom2)
        self.assertEqual(str(atom1), str(atom2))

    def test_from_dtype_02(self):
        atom1 = Atom.from_dtype(numpy.dtype('S5'), dflt=b'hello')
        atom2 = StringAtom(itemsize=5, shape=(), dflt=b'hello')
        self.assertEqual(atom1, atom2)
        self.assertEqual(str(atom1), str(atom2))

    def test_from_dtype_03(self):
        atom1 = Atom.from_dtype(numpy.dtype('Float64'))
        atom2 = Float64Atom(shape=(), dflt=0.0)
        self.assertEqual(atom1, atom2)
        self.assertEqual(str(atom1), str(atom2))

    def test_from_kind_01(self):
        atom1 = Atom.from_kind('int', itemsize=2, shape=(2, 2))
        atom2 = Int16Atom(shape=(2, 2), dflt=0)
        self.assertEqual(atom1, atom2)
        self.assertEqual(str(atom1), str(atom2))

    def test_from_kind_02(self):
        atom1 = Atom.from_kind('int', shape=(2, 2))
        atom2 = Int32Atom(shape=(2, 2), dflt=0)
        self.assertEqual(atom1, atom2)
        self.assertEqual(str(atom1), str(atom2))

    def test_from_kind_03(self):
        atom1 = Atom.from_kind('int', shape=1)
        atom2 = Int32Atom(shape=(1,), dflt=0)
        self.assertEqual(atom1, atom2)
        self.assertEqual(str(atom1), str(atom2))

    def test_from_kind_04(self):
        atom1 = Atom.from_kind('string', itemsize=5, dflt=b'hello')
        atom2 = StringAtom(itemsize=5, shape=(), dflt=b'hello')
        self.assertEqual(atom1, atom2)
        self.assertEqual(str(atom1), str(atom2))

    def test_from_kind_05(self):
        # ValueError: no default item size for kind ``string``
        self.assertRaises(ValueError, Atom.from_kind, 'string', dflt=b'hello')

    def test_from_kind_06(self):
        # ValueError: unknown kind: 'Float'
        self.assertRaises(ValueError, Atom.from_kind, 'Float')


def suite():
    import doctest
    import tables.atom

    theSuite = unittest.TestSuite()

    for i in range(1):
        theSuite.addTest(doctest.DocTestSuite(tables.atom))
        theSuite.addTest(unittest.makeSuite(AtomTestCase))
        theSuite.addTest(unittest.makeSuite(RangeTestCase))
        theSuite.addTest(unittest.makeSuite(DtypeTestCase))
        theSuite.addTest(unittest.makeSuite(ReadFloatTestCase))

    return theSuite


if __name__ == '__main__':
    common.parse_argv(sys.argv)
    common.print_versions()
    unittest.main(defaultTest='suite')
