# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
# 
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
# 
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

import unittest
import random

test= bpy.data.test

# farr - 1-dimensional array of float
# fdarr - dynamic 1-dimensional array of float
# fmarr - 3-dimensional ([3][4][5]) array of float
# fdmarr - dynamic 3-dimensional (ditto size) array of float

# same as above for other types except that the first letter is "i" for int and "b" for bool

class TestArray(unittest.TestCase):
    # test that assignment works by: assign -> test value
    # - rvalue = list of float
    # - rvalue = list of numbers
    # test.object
    # bpy.data.test.farr[3], iarr[3], barr[...], fmarr, imarr, bmarr

    def setUp(self):
        test.farr= (1.0, 2.0, 3.0)
        test.iarr= (7, 8, 9)
        test.barr= (False, True, False)
    
    # test access
    # test slice access, negative indices
    def test_access(self):
        rvals= ([1.0, 2.0, 3.0], [7, 8, 9], [False, True, False])
        for arr, rval in zip((test.farr, test.iarr, test.barr), rvals):
            self.assertEqual(prop_to_list(arr), rval)
            self.assertEqual(arr[0:3], rval)
            self.assertEqual(arr[1:2], rval[1:2])
            self.assertEqual(arr[-1], arr[2])
            self.assertEqual(arr[-2], arr[1])
            self.assertEqual(arr[-3], arr[0])

    # fail when index out of bounds
    def test_access_fail(self):
        for arr in (test.farr, test.iarr, test.barr):
            self.assertRaises(IndexError, lambda : arr[4])
    
    # test assignment of a whole array
    def test_assign_array(self):
        # should accept int as float
        test.farr= (1, 2, 3)

    # fail when: unexpected no. of items, invalid item type
    def test_assign_array_fail(self):
        def assign_empty_list(arr):
            setattr(test, arr, ())

        for arr in ("farr", "iarr", "barr"):
            self.assertRaises(ValueError, assign_empty_list, arr)

        def assign_invalid_float():
            test.farr= (1.0, 2.0, "3.0")

        def assign_invalid_int():
            test.iarr= ("1", 2, 3)

        def assign_invalid_bool():
            test.barr= (True, 0.123, False)

        for func in [assign_invalid_float, assign_invalid_int, assign_invalid_bool]:
            self.assertRaises(TypeError, func)

        # shouldn't accept float as int
        def assign_float_as_int():
            test.iarr= (1, 2, 3.0)
        self.assertRaises(TypeError, assign_float_as_int)

        # non-dynamic arrays cannot change size
        def assign_different_size(arr, val):
            setattr(test, arr, val)
        for arr, val in zip(("iarr", "farr", "barr"), ((1, 2), (1.0, 2.0), (True, False))):
            self.assertRaises(ValueError, assign_different_size, arr, val)

    # test assignment of specific items
    def test_assign_item(self):
        for arr, rand_func in zip((test.farr, test.iarr, test.barr), (rand_float, rand_int, rand_bool)):
            for i in range(len(arr)):
                val= rand_func()
                arr[i] = val
                
                self.assertEqual(arr[i], val)

        # float prop should accept also int
        for i in range(len(test.farr)):
            val= rand_int()
            test.farr[i] = val
            self.assertEqual(test.farr[i], float(val))

        # 

    def test_assign_item_fail(self):
        def assign_bad_index(arr):
            arr[4] = 1.0

        def assign_bad_type(arr):
            arr[1] = "123"
            
        for arr in [test.farr, test.iarr, test.barr]:
            self.assertRaises(IndexError, assign_bad_index, arr)

        # not testing bool because bool allows not only (True|False)
        for arr in [test.farr, test.iarr]:    
            self.assertRaises(TypeError, assign_bad_type, arr)

    def test_dynamic_assign_array(self):
        # test various lengths here
        for arr, rand_func in zip(("fdarr", "idarr", "bdarr"), (rand_float, rand_int, rand_bool)):
            for length in range(1, 64):
                rval= make_random_array(length, rand_func)
                setattr(test, arr, rval)
                self.assertEqual(prop_to_list(getattr(test, arr)), rval)

    def test_dynamic_assign_array_fail(self):
        # could also test too big length here
        
        def assign_empty_list(arr):
            setattr(test, arr, ())

        for arr in ("fdarr", "idarr", "bdarr"):
            self.assertRaises(ValueError, assign_empty_list, arr)


class TestMArray(unittest.TestCase):
    def setUp(self):
        # reset dynamic array sizes
        for arr, func in zip(("fdmarr", "idmarr", "bdmarr"), (rand_float, rand_int, rand_bool)):
            setattr(test, arr, make_random_3d_array((3, 4, 5), func))

    # test assignment
    def test_assign_array(self):
        for arr, func in zip(("fmarr", "imarr", "bmarr"), (rand_float, rand_int, rand_bool)):
            # assignment of [3][4][5]
            rval= make_random_3d_array((3, 4, 5), func)
            setattr(test, arr, rval)
            self.assertEqual(prop_to_list(getattr(test, arr)), rval)

        # test assignment of [2][4][5], [1][4][5] should work on dynamic arrays

    def test_assign_array_fail(self):
        def assign_empty_array():
            test.fmarr= ()
        self.assertRaises(ValueError, assign_empty_array)

        def assign_invalid_size(arr, rval):
            setattr(test, arr, rval)

        # assignment of 3,4,4 or 3,3,5 should raise ex
        for arr, func in zip(("fmarr", "imarr", "bmarr"), (rand_float, rand_int, rand_bool)):
            rval= make_random_3d_array((3, 4, 4), func)
            self.assertRaises(ValueError, assign_invalid_size, arr, rval)

            rval= make_random_3d_array((3, 3, 5), func)
            self.assertRaises(ValueError, assign_invalid_size, arr, rval)

            rval= make_random_3d_array((3, 3, 3), func)
            self.assertRaises(ValueError, assign_invalid_size, arr, rval)

    def test_assign_item(self):
        # arr[i] = x
        for arr, func in zip(("fmarr", "imarr", "bmarr", "fdmarr", "idmarr", "bdmarr"), (rand_float, rand_int, rand_bool) * 2):
            rval= make_random_2d_array((4, 5), func)

            for i in range(3):
                getattr(test, arr)[i] = rval
                self.assertEqual(prop_to_list(getattr(test, arr)[i]), rval)

        # arr[i][j] = x
        for arr, func in zip(("fmarr", "imarr", "bmarr", "fdmarr", "idmarr", "bdmarr"), (rand_float, rand_int, rand_bool) * 2):

            arr= getattr(test, arr)
            rval= make_random_array(5, func)

            for i in range(3):
                for j in range(4):
                    arr[i][j] = rval
                    self.assertEqual(prop_to_list(arr[i][j]), rval)


    def test_assign_item_fail(self):
        def assign_wrong_size(arr, i, rval):
            getattr(test, arr)[i] = rval

        # assign wrong size at level 2
        for arr, func in zip(("fmarr", "imarr", "bmarr"), (rand_float, rand_int, rand_bool)):
            rval1= make_random_2d_array((3, 5), func)
            rval2= make_random_2d_array((4, 3), func)

            for i in range(3):
                self.assertRaises(ValueError, assign_wrong_size, arr, i, rval1)
                self.assertRaises(ValueError, assign_wrong_size, arr, i, rval2)

    def test_dynamic_assign_array(self):
        for arr, func in zip(("fdmarr", "idmarr", "bdmarr"), (rand_float, rand_int, rand_bool)):
            # assignment of [3][4][5]
            rval= make_random_3d_array((3, 4, 5), func)
            setattr(test, arr, rval)
            self.assertEqual(prop_to_list(getattr(test, arr)), rval)

            # [2][4][5]
            rval= make_random_3d_array((2, 4, 5), func)
            setattr(test, arr, rval)
            self.assertEqual(prop_to_list(getattr(test, arr)), rval)

            # [1][4][5]
            rval= make_random_3d_array((1, 4, 5), func)
            setattr(test, arr, rval)
            self.assertEqual(prop_to_list(getattr(test, arr)), rval)


    # test access
    def test_access(self):
        pass

    # test slice access, negative indices
    def test_access_fail(self):
        pass

random.seed()

def rand_int():
    return random.randint(-1000, 1000)

def rand_float():
    return float(rand_int())

def rand_bool():
    return bool(random.randint(0, 1))

def make_random_array(len, rand_func):
    arr= []
    for i in range(len):
        arr.append(rand_func())
        
    return arr

def make_random_2d_array(dimsize, rand_func):
    marr= []
    for i in range(dimsize[0]):
        marr.append([])

        for j in range(dimsize[1]):
            marr[-1].append(rand_func())

    return marr

def make_random_3d_array(dimsize, rand_func):
    marr= []
    for i in range(dimsize[0]):
        marr.append([])

        for j in range(dimsize[1]):
            marr[-1].append([])

            for k in range(dimsize[2]):
                marr[-1][-1].append(rand_func())

    return marr

def prop_to_list(prop):
    ret= []

    for x in prop:
        if type(x) not in (bool, int, float):
            ret.append(prop_to_list(x))
        else:
            ret.append(x)

    return ret

def suite():
    return unittest.TestSuite([unittest.TestLoader().loadTestsFromTestCase(TestArray), unittest.TestLoader().loadTestsFromTestCase(TestMArray)])

if __name__ == "__main__":
    unittest.TextTestRunner(verbosity=2).run(suite())

