# csv_file.py
# Copyright (c) 2013-2016 Pablo Acosta-Serafini
# See LICENSE for details
# pylint: disable=C0103,C0111,C0302,E0611,F0401,R0201,R0204,R0915,W0232

# Standard library imports
import os
import sys
if sys.hexversion >= 0x03000000:
    import unittest.mock as mock
# PyPI imports
import pytest
if sys.hexversion < 0x03000000:
    import mock
# Putil imports
import putil.misc
import putil.pcsv
from putil.test import AE, AI, APROP, AROPROP, GET_EXMSG, RE
if sys.hexversion < 0x03000000:
    from putil.compat2 import _read
else:
    from putil.compat3 import _read
from tests.pcsv.fixtures import (
    write_cols_not_unique,
    write_data_start_file,
    write_empty_cols,
    write_file,
    write_file_empty,
    write_no_data,
    write_no_header_file,
    write_sort_file,
    write_str_cols_file
)


###
# Global variables
###
SEP = os.path.abspath(os.sep)


###
# Test classes
###
class TestCsvFile(object):
    """ Tests for CsvFile class """
    # pylint: disable=R0904
    @pytest.mark.csv_file
    def test_init_exceptions(self):
        """ Test constructor exceptions """
        obj = putil.pcsv.CsvFile
        fname = os.path.join(SEP, 'file', 'does', 'not', 'exists.csv')
        func_pointers = [
            (RE, 'File {0} is empty', write_file_empty),
            (
                RE,
                'Column headers are not unique in file {0}',
                write_cols_not_unique
            ),
            (RE, 'File {0} has no valid data', write_no_data)
        ]
        AI(obj, 'fname', fname=5)
        exmsg = 'File {0} could not be found'.format(fname)
        AE(obj, OSError, exmsg, fname=fname)
        for extype, exmsg, fobj in func_pointers:
            with pytest.raises(extype) as excinfo:
                with putil.misc.TmpFile(fobj) as fname:
                    putil.pcsv.CsvFile(fname=os.path.normpath(fname))
            ref = (exmsg.format(fname) if '{0}' in exmsg else exmsg)
            assert GET_EXMSG(excinfo) == os.path.normpath(ref)
        with putil.misc.TmpFile(write_file) as fname:
            AI(obj, 'has_header', fname=fname, has_header=5)
        with putil.misc.TmpFile(write_file) as fname:
            AI(obj, 'dfilter', fname=fname, dfilter=5.2)
            AE(obj, ValueError, 'Column 5 not found', fname, 5, True)
            AE(obj, ValueError, 'Column 5 not found', fname, 5, False)
            AE(obj, RE, 'Invalid column specification', fname, 'A', False)
            AE(obj, ValueError, 'Column aa not found', fname, 'aa')

    def test_eq(self):
        """ Test __eq__ method behavior """
        # pylint: disable=C0113
        with putil.misc.TmpFile() as fname:
            putil.pcsv.write(fname, [['a'], [1]], append=False)
            obj1 = putil.pcsv.CsvFile(fname, dfilter='a')
            obj2 = putil.pcsv.CsvFile(fname, dfilter='a')
        with putil.misc.TmpFile() as fname:
            putil.pcsv.write(fname, [['a'], [2]], append=False)
            obj3 = putil.pcsv.CsvFile(fname, dfilter='a')
        assert obj1 == obj2
        assert obj1 != obj3
        assert not obj3 == 5

    def test_repr(self):
        """ Test __repr__ method behavior """
        with putil.misc.TmpFile(write_file) as fname:
            obj = putil.pcsv.CsvFile(fname=fname)
        fname = os.path.normpath(fname)
        rstart = "putil.pcsv.CsvFile(fname=r'{0}'"
        assert repr(obj) == (rstart+')').format(fname)
        obj.dfilter = 'Ctrl'
        assert repr(obj) == (rstart+", dfilter=['Ctrl'])").format(fname)
        obj.dfilter = {'Ctrl':2}
        assert repr(obj) == (rstart+", dfilter={{'Ctrl': 2}})").format(fname)
        obj.dfilter = ('Ctrl', {'Result': 40})
        assert repr(obj) == (rstart+", dfilter={1})").format(
            fname, "({'Result': 40}, ['Ctrl'])"
        )
        with putil.misc.TmpFile(write_file) as fname:
            obj = putil.pcsv.CsvFile(fname=fname, has_header=False)
        fname = os.path.normpath(fname)
        assert repr(obj) == (rstart+", has_header=False)").format(fname)
        obj.dfilter = 0
        assert repr(obj) == (rstart+", dfilter={1}, has_header=False)").format(
            fname, '[0]'
        )
        obj.dfilter = {0:2}
        assert repr(obj) == (rstart+", dfilter={1}, has_header=False)").format(
            fname, '{0: 2}'
        )
        obj.dfilter = (0, {2: 40})
        assert repr(obj) == (rstart+', dfilter={1}, has_header=False)').format(
            fname, '({2: 40}, [0])'
        )

    def test_str(self):
        """ Test __str__ method behavior """
        with putil.misc.TmpFile(write_file) as fname:
            obj = putil.pcsv.CsvFile(fname=os.path.normpath(fname))
        ref = (
            'File: {0}\n'
            "Header: ['Ctrl', 'Ref', 'Result']\n"
            'Row filter: None\n'
            'Column filter: None\n'
            'Rows: 5\n'
            'Columns: 3'.format(os.path.normpath(fname))
        )
        assert str(obj) == ref
        obj.cfilter = 'Ref'
        ref = (
            'File: {0}\n'
            "Header: ['Ctrl', 'Ref', 'Result']\n"
            'Row filter: None\n'
            "Column filter: ['Ref']\n"
            'Rows: 5\n'
            'Columns: 3 (1 filtered)'.format(os.path.normpath(fname))
        )
        assert str(obj) == ref
        obj.rfilter = {'Ctrl':[1, 3]}
        ref = (
            'File: {0}\n'
            "Header: ['Ctrl', 'Ref', 'Result']\n"
            "Row filter: {{'Ctrl': [1, 3]}}\n"
            "Column filter: ['Ref']\n"
            'Rows: 5 (3 filtered)\n'
            'Columns: 3 (1 filtered)'.format(os.path.normpath(fname))
        )
        assert str(obj) == ref

    def test_data_start(self):
        """ Test if the right row is picked for the data start """
        with putil.misc.TmpFile(write_data_start_file) as fname:
            obj = putil.pcsv.CsvFile(fname=fname)
        assert obj.data() == [[2, None, 30], [2, 5, 40], [3, 5, 50]]
        with putil.misc.TmpFile(write_no_header_file) as fname:
            obj = putil.pcsv.CsvFile(fname=fname, has_header=False)
        assert obj.data() == [[1, 4, 7], [2, 5, 8], [3, 6, 9], [1, 6, 6]]
        with putil.misc.TmpFile(write_data_start_file) as fname:
            obj = putil.pcsv.CsvFile(fname=fname, frow=5)
        assert obj.data() == [[2, 5, 40], [3, 5, 50]]
        with putil.misc.TmpFile(write_no_header_file) as fname:
            obj = putil.pcsv.CsvFile(fname=fname, has_header=False, frow=2)
        assert obj.data() == [[2, 5, 8], [3, 6, 9], [1, 6, 6]]

    def test_data_start_exceptions(self):
        """ Test frow parameter exceptions """
        obj = putil.pcsv.CsvFile
        with putil.misc.TmpFile(write_data_start_file) as fname:
            for item in ['a', True, -1]:
                AI(obj, 'frow', fname, frow=item)
            exmsg = 'File {0} has no valid data'.format(fname)
            AE(obj, RE, exmsg, fname, frow=10)

    def test_add_dfilter(self):
        """ Test add_dfilter method behavior """
        with putil.misc.TmpFile(write_file) as fname:
            obj = putil.pcsv.CsvFile(fname=fname)
            obj.add_dfilter(None)
            ref = [[1, 3, 10], [1, 4, 20], [2, 4, 30], [2, 5, 40], [3, 5, 50]]
            assert obj.data(filtered=True) == ref
            obj.add_dfilter({'Ctrl':1})
            obj.add_dfilter({'Result':20})
            assert obj.data(filtered=True) == [[1, 4, 20]]
            obj.add_dfilter(1)
            assert obj.data(filtered=True) == [[4]]
            obj.reset_dfilter()
            assert obj.data(filtered=True) == ref
            obj.add_dfilter(({'Result':20}, 1))
            assert obj.data(filtered=True) == [[4]]
            # Two single elements
            obj = putil.pcsv.CsvFile(fname=fname, dfilter={'Result':20})
            assert obj.data(filtered=True) == [[1, 4, 20]]
            obj.add_dfilter({'Result':40})
            assert obj.data(filtered=True) == [[1, 4, 20], [2, 5, 40]]
            # Single element to list
            obj.reset_dfilter('R')
            obj.dfilter = {'Result':[10, 30]}
            assert obj.data(filtered=True) == [[1, 3, 10], [2, 4, 30]]
            obj.add_dfilter({'Result':50})
            assert (
                obj.data(filtered=True) == [[1, 3, 10], [2, 4, 30], [3, 5, 50]]
            )
            obj.add_dfilter(([0, 2], {'Result':50}))
            assert (obj.data(filtered=True) == [[1, 10], [2, 30], [3, 50]])