#!/usr/bin/env python
# pylint: disable=R0904,W0142,C0103

"""Unit Tests for the core data structures"""

import csv
import StringIO
import unittest

try:
    # Help bootstrap (If PYTHONPATH isn't set); pylint: disable=W0611
    from bootstrap import lemonade
except ImportError:
    pass
from lemonade import models


class ChartOfAccountsTestCase(unittest.TestCase):
    """ChartOfAccounts Tests

    The ChartOfAccounts is the authoritative source of all account
    numbers and account titles.
    """

    def build_chart_of_accounts(self):
        """Build default factory for chart of accounts"""

        # This could be its own function outside of the test
        # It's currently cleaner to leave it here
        # pylint: disable=R0201

        return models.ChartOfAccounts(raw_data=[
            ('1010', 'Petty Cash'),
            ('2000', 'Accounts Payable')
        ])

    def build_csv_IO_handler(self, contents):
        """Given contents, create a CSV file representation

        The caller is responsible for closing returned handler.
        """

        # This could be its own function outside of the test
        # It's currently cleaner to leave it here
        # pylint: disable=R0201

        write_handler = StringIO.StringIO()
        spreadsheet = csv.writer(write_handler, dialect='excel')
        for row in contents:
            spreadsheet.writerow(row)

        read_handler = StringIO.StringIO(write_handler.getvalue())
        write_handler.close()

        return read_handler

    def test_should_initialize_wo_args(self):
        """It should initialize without arguments

        One can have an empty ChartOfAccounts. It would be a
        very boring chart and no real accounting work can be
        done. But, it is possible.
        """
        # No assertion is needed as long as no errors are raised
        # pylint: disable=R0201
        models.ChartOfAccounts()

    def test_should_initialize_w_args(self):
        """It should initialize with arguments

        If one passes an argument (internally referred to as
        'should_iterate'), then that argument will be saved as a
        representation of the ChartOfAccounts.

        This test does not care what should_iterate is (as long as its
        not None). However, it makes for clearer documentation to show a
        real example.
        """
        # No assertion is needed as long as no errors are raised
        self.build_chart_of_accounts()

    def test_initialize_argument_should_be_iterable(self):
        """Initialize argument should be iterable

        Initializing ChartOfAccounts only makes sense if the argument
        given is iterable. If it is not, then raise a
        ChartOfAccountsFormatException exception.
        """
        self.assertRaises(models.ChartOfAccountsFormatException,
                          models.ChartOfAccounts, raw_data=13)

    def test_initialize_argument_elements_should_be_at_least_two(self):
        """Initialize argument elements should be at least two in length

        As we iterate through the elements within the ChartOfAccounts,
        when initializing, those elements need to be at least two fields
        long (the first for the account id and the second for the
        account title). If this is not true, a
        ChartOfAccountsFormatException is raised.
        """
        self.assertRaises(models.ChartOfAccountsFormatException,
                          models.ChartOfAccounts,
                          raw_data=[['1010'], ['1020']])

    def test_should_retrieve_title_by_account_number(self):
        """It should retrieve title by account number"""

        coa = self.build_chart_of_accounts()
        self.assertEqual(coa.get_title('2000'), "Accounts Payable")

    def test_account_nums_can_be_numeric_or_strings(self):
        """Account Numbers can be Numeric or String

        When accessing Account Titles (with get_title), the input given
        can be numeric or string. Internally, they will always be stored
        as strings. Be careful of preceeding zeros since we do not
        currently take this into consideration.
        """

        coa = self.build_chart_of_accounts()
        self.assertEqual(coa.get_title('1010'), "Petty Cash")
        self.assertEqual(coa.get_title(2000), "Accounts Payable")

    def test_requesting_title_for_unknown_number_raises_exception(self):
        """Requesting title for an unknown number raises exception

        If get_title() is requested for an account number that doesn't
        exist, then MissingAccountException is raised.
        """
        coa = self.build_chart_of_accounts()
        self.assertRaises(models.MissingAccountException,
                          coa.get_title, '9000')

    def test_should_output_csv_format_for_files(self):
        """It should output CSV format for files"""

        coa = self.build_chart_of_accounts()
        output_file = StringIO.StringIO()

        # At this point output_file is CSV
        coa.to_csv(output_file=output_file)
        input_file = StringIO.StringIO(output_file.getvalue())
        output_file.close()
        csv_file = csv.reader(input_file, dialect='excel')

        row1 = csv_file.next()
        self.assertEqual(row1[0], '1010')
        self.assertEqual(row1[1], 'Petty Cash')

        row2 = csv_file.next()
        self.assertEqual(row2[0], '2000')
        self.assertEqual(row2[1], 'Accounts Payable')

        input_file.close()

    def test_should_input_csv_format_from_files(self):
        """It should input CSV format from files"""

        coa = models.ChartOfAccounts()
        csv_file = self.build_csv_IO_handler([
            ('4040', 'Services'),
            ('5092', 'Postage and Delivery'),
        ])

        coa.from_csv(input_file=csv_file)
        csv_file.close()
        self.assertEqual(coa.get_title('4040'), "Services")
        self.assertEqual(coa.get_title(5092), "Postage and Delivery")

    def test_should_support_iteration(self):
        """It should support iteration"""
        coa = self.build_chart_of_accounts()
        generated = coa.__iter__()
        self.assertEqual(generated.next(), ('1010', 'Petty Cash'))
        self.assertEqual(generated.next(), ('2000', 'Accounts Payable'))


if __name__ == '__main__':
    unittest.main()
