if __name__ == '__main__':
    from   os.path import dirname, abspath, join
    import sys
    sys.path.append( abspath( join( dirname(__file__), '..' )))


import unittest

from palm.PalmConfig import PalmConfig


class PalmConfigTests(unittest.TestCase):

    ################################################# test palm-config-rule ###

    def test_rule_init_none(self):

        rule = PalmConfig.Rule()

        self.assertEqual( rule.name,     None )
        self.assertEqual( rule.path,     None )
        self.assertEqual( rule.pathname, None )
        self.assertEqual( rule.default,  None )

    def test_rule_init_some(self):

        rule = PalmConfig.Rule(
            'name', ['path','to','name'],
            default='default'
            )

        self.assertEqual( rule.name,     'name'               )
        self.assertEqual( rule.path,     ['path','to','name'] )
        self.assertEqual( rule.pathname, 'path_to_name'       )
        self.assertEqual( rule.default,  'default'            )

    def test_rule_init_all(self):

        rule = PalmConfig.Rule(
            'name', ['path','to','name'],
            lambda name, value: '{}: {}'.format(name,value),
            lambda name, value: not value is None,
            'default'
            )

        self.assertEqual( rule.name,     'name'               )
        self.assertEqual( rule.path,     ['path','to','name'] )
        self.assertEqual( rule.pathname, 'path_to_name'       )
        self.assertEqual( rule.default,  'default'            )

    def test_rule_validate(self):

        rule = PalmConfig.Rule(
            'name', ['path','to','name'],
            lambda name, value: '{}: {}'.format(name,value),
            lambda name, value: not value is None,
            'default'
            )

        self.assertEqual( rule.validate( 'n', None), False )
        self.assertEqual( rule.validate( 'n', 1234), True  )

    def test_rule_is_immutable(self):

        rule = PalmConfig.Rule()

        with self.assertRaises(Exception): # no change
            rule.name    = 'name'

        with self.assertRaises(Exception): # no new values
            rule.unknown = 'unknown'

    # ########################################################### palm-config ###

    def test_init_none(self):

        palm = PalmConfig()

        self.assertEqual( palm.override, True   )
        self.assertEqual( len(palm),     0      )
        self.assertFalse( palm                  )

    def test_init_parameter(self):

        palm = PalmConfig(
            { 'first':  1, 'forth_item': 4 },
            { 'second': 2 },
            rules=[
                PalmConfig.Rule( 'third',                   default=3 ),
                PalmConfig.Rule( 'forth', ['forth','item'], default=-4 )
                ],
            override=True
            )

        self.assertEqual( palm.override, True   )
        self.assertEqual( len(palm),     4      ) # count 'forth' only once
        self.assertTrue ( palm                  )

    def test_in(self):

        palm = PalmConfig(
            { 'first':  1, 'forth_item': 4 }, # managed
            { 'second': 2 },                  # defaults
            rules=[
                PalmConfig.Rule( 'third',                   default=3 ),
                PalmConfig.Rule( 'forth', ['forth','item'], default=-4 )
                ]
            )

        self.assertEqual( 'first'      in palm,  True  )
        self.assertEqual( 'forth_item' in palm,  True  )
        self.assertEqual( 'second'     in palm,  True  )
        self.assertEqual( 'third'      in palm,  True  )

        self.assertEqual( 'forth'      in palm,  True  )
        self.assertEqual( 'forth_item' in palm,  True  )

        self.assertEqual( 'fifth'      in palm,  False )

    def test_managing(self):

        palm = PalmConfig(
            { 'first':  1, 'forth_item': 4 }, # managed
            { 'second': 2 },                  # defaults
            rules=[
                PalmConfig.Rule( 'third',                   default=3 ),
                PalmConfig.Rule( 'forth', ['forth','item'], default=-4 )
                ]
            )

        self.assertEqual( palm.managing('first'),       True  )
        self.assertEqual( palm.managing('forth_item'),  True  )

        self.assertEqual( palm.managing('second'),      False )
        self.assertEqual( palm.managing('third'),       False )

        self.assertEqual( palm.managing('forth'),       True  )
        self.assertEqual( palm.managing('forth_item'),  True  )

        self.assertEqual( palm.managing('fifth'),       False )

    def test_get(self):

        palm = PalmConfig(
            { 'first':  1, 'forth_item': 5 }, # managed
            { 'second': 2 },                  # defaults
            rules=[
                PalmConfig.Rule( 'third',                   default=3 ),
                PalmConfig.Rule( 'forth', ['forth','item'], default=-4 )
                ]
            )

        self.assertEqual( palm.first,        1    )
        self.assertEqual( palm['first'],     1    )
        self.assertEqual( palm.get('first'), 1    )

        self.assertEqual( palm.second,       2    )

        self.assertEqual( palm.third,        3    )
        self.assertEqual( palm['third'],     3    )
        self.assertEqual( palm.get('third'), 3    )

        self.assertEqual( palm.forth,        5    ) # found by rule.pathname
        self.assertEqual( palm.forth_item,   5    )

        self.assertEqual( palm.fifth, None ) # but
        with self.assertRaises(KeyError):
            palm['fifth']


    def test_get_default(self):

        palm = PalmConfig(
            { 'first':  1, 'forth_item': 5 }, # managed
            { 'second': 2, 'sixth': None   }, # defaults
            rules=[
                PalmConfig.Rule( 'third',                   default=3 ),
                PalmConfig.Rule( 'forth', ['forth','item'], default=-4 )
                ]
            )

        self.assertEqual( palm.get('first',  6),  1 )
        self.assertEqual( palm.get('second', 6),  2 )
        self.assertEqual( palm.get('third',  6),  3 )
        self.assertEqual( palm.get('forth',  6),  5 )

        self.assertEqual( palm.get('fifth',  6),  6 )

        self.assertEqual( palm.get('sixth',  6),  None ) # sixth in palm

    def test_set(self):

        palm = PalmConfig(
            { 'first':  1, 'forth_item': 4 }, # managed
            { 'second': 2, 'sixth': None   }, # defaults
            rules=[
                PalmConfig.Rule( 'third',                   default=3 ),
                PalmConfig.Rule( 'forth', ['forth','item'], default=-4 )
                ],
            override=False
            )

        self.assertEqual( palm.override,    False ) # can't override existing

        palm.first  = 6; self.assertEqual( palm.first,  1 )

        palm.second = 6; self.assertEqual( palm.second, 6 ) # not in managed
        palm.third  = 6; self.assertEqual( palm.third,  6 ) # not in managed

        palm.forth  = 6; self.assertEqual( palm.forth,  4 )

        palm.fifth  = 6; self.assertEqual( palm.fifth,  6 ) # not in managed

        # but attribute 'override' ...
        #
        self.assertEqual( palm.override,    False )
        palm.override = True
        self.assertEqual( palm.override,    True  )

    def test_set_with_override(self):

        palm = PalmConfig(
            { 'first':  1, 'forth_item': 4 },
            { 'second': 2 },
            rules=[
                PalmConfig.Rule( 'third',                   default=3 ),
                PalmConfig.Rule( 'forth', ['forth','item'], default=-4 )
                ],
            override=True
            )

        self.assertEqual( palm.override, True ) # can override existing

        palm.first  = 6; self.assertEqual( palm.first,  6 )
        palm.second = 6; self.assertEqual( palm.second, 6 )
        palm.third  = 6; self.assertEqual( palm.third,  6 )
        palm.forth  = 6; self.assertEqual( palm.forth,  6 )

        palm.fifth  = 6; self.assertEqual( palm.fifth,  6 )

        # but attribute 'override' ...
        #
        self.assertEqual( palm.override, True )
        palm.override = False
        self.assertEqual( palm.override, False )

    def test_set_validate(self):
        # TODO: write test
        pass

    def test_update(self):

        palm = PalmConfig(
            { 'first':  1, 'forth_item': 4 }, # managed
            { 'second': 2, 'sixth': None   }, # defaults
            rules=[
                PalmConfig.Rule( 'third',                   default=3 ),
                PalmConfig.Rule( 'forth', ['forth','item'], default=-4 )
                ],
            override=False
            )

        self.assertEqual( palm.override, False ) # can't override existing

        self.assertEqual( palm.first,      1 )
        self.assertEqual( palm.second,     2 )
        self.assertEqual( palm.third,      3 )
        self.assertEqual( palm.forth,      4 )
        self.assertEqual( palm.forth_item, 4 )

        self.assertEqual( palm.fifth, None ) # but
        with self.assertRaises(KeyError):
            palm['fifth']

        palm.update({'first':5, 'second':5, 'fifth':5})
        palm.update( third=5, forth=5 )

        self.assertEqual( palm.first,      1 )
        self.assertEqual( palm.second,     5 )
        self.assertEqual( palm.third,      5 )
        self.assertEqual( palm.forth,      4 )
        self.assertEqual( palm.forth_item, 4 )
        self.assertEqual( palm.fifth,      5 )

    def test_update_with_override(self):

        palm = PalmConfig(
            { 'first':  1, 'forth_item': 4 }, # managed
            { 'second': 2, 'sixth': None   }, # defaults
            rules=[
                PalmConfig.Rule( 'third',                   default=3 ),
                PalmConfig.Rule( 'forth', ['forth','item'], default=-4 )
                ],
            override=True
            )

        self.assertEqual( palm.override, True ) # can override existing

        self.assertEqual( palm.first,      1 )
        self.assertEqual( palm.second,     2 )
        self.assertEqual( palm.third,      3 )
        self.assertEqual( palm.forth,      4 )
        self.assertEqual( palm.forth_item, 4 )

        self.assertEqual( palm.fifth, None ) # but
        with self.assertRaises(KeyError):
            palm['fifth']

        palm.update({'first':5, 'second':5, 'fifth':5})
        palm.update( third=5, forth=5 )

        self.assertEqual( palm.first,      5 )
        self.assertEqual( palm.second,     5 )
        self.assertEqual( palm.third,      5 )
        self.assertEqual( palm.forth,      5 )
        self.assertEqual( palm.forth_item, 5 )
        self.assertEqual( palm.fifth,      5 )

    def test_del(self):

        palm = PalmConfig(
            { 'first':  1, 'forth_item': 4 }, # managed
            { 'second': 2, 'sixth': None   }, # defaults
            rules=[
                PalmConfig.Rule( 'third',                   default=3 ),
                PalmConfig.Rule( 'forth', ['forth','item'], default=-4 )
                ],
            override=False
            )

        self.assertEqual( palm.override, False ) # can't override existing

        self.assertEqual( palm.first,      1 )
        self.assertEqual( palm.second,     2 )
        self.assertEqual( palm.third,      3 )
        self.assertEqual( palm.forth,      4 )
        self.assertEqual( palm.forth_item, 4 )

        self.assertEqual( palm.fifth, None ) # but
        with self.assertRaises(KeyError):
            palm['fifth']


        del palm['first']

        with self.assertRaises(KeyError):
            del palm.second

        with self.assertRaises(KeyError):
            del palm['thrid']

        del palm.forth
        del palm['forth_item']

        with self.assertRaises(KeyError):
            del palm['fifth']
        with self.assertRaises(KeyError):
            del palm.fifth

        self.assertEqual( palm.first,      1 )
        self.assertEqual( palm.second,     2 )
        self.assertEqual( palm.third,      3 )
        self.assertEqual( palm.forth,      4 )
        self.assertEqual( palm.forth_item, 4 )

        self.assertEqual( palm.fifth, None ) # but
        with self.assertRaises(KeyError):
            palm['fifth']

    def test_del_with_override(self):

        palm = PalmConfig(
            { 'first':  1, 'forth_item': 4 }, # managed
            { 'second': 2, 'sixth': None   }, # defaults
            rules=[
                PalmConfig.Rule( 'third',                   default=3 ),
                PalmConfig.Rule( 'forth', ['forth','item'], default=-4 )
                ],
            override=True
            )

        self.assertEqual( palm.override, True ) # can't override existing

        self.assertEqual( palm.first,      1 )
        self.assertEqual( palm.second,     2 )
        self.assertEqual( palm.third,      3 )
        self.assertEqual( palm.forth,      4 )
        self.assertEqual( palm.forth_item, 4 )

        self.assertEqual( palm.fifth, None ) # but
        with self.assertRaises(KeyError):
            palm['fifth']


        del palm['first']

        with self.assertRaises(KeyError):
            del palm.second

        with self.assertRaises(KeyError):
            del palm['thrid']

        del palm.forth
        with self.assertRaises(KeyError):
            del palm['forth_itme']

        with self.assertRaises(KeyError):
            del palm['fifth']
        with self.assertRaises(KeyError):
            del palm.fifth

        self.assertEqual( palm.first, None ) # but
        with self.assertRaises(KeyError):
            palm['first']

        self.assertEqual( palm.second,      2 )
        self.assertEqual( palm.third,       3 )
        self.assertEqual( palm.forth,      -4 )
        self.assertEqual( palm.forth_item, -4 )

        self.assertEqual( palm.fifth, None ) # but
        with self.assertRaises(KeyError):
            palm['fifth']

    def test_clear(self):

        palm = PalmConfig(
            { 'first':  1, 'forth_item': 4 }, # managed
            { 'second': 2, 'sixth': None   }, # defaults
            rules=[
                PalmConfig.Rule( 'third',                   default=3 ),
                PalmConfig.Rule( 'forth', ['forth','item'], default=-4 )
                ],
            override=False
            )

        self.assertEqual( palm.override, False ) # can't override existing

        self.assertEqual( palm.first,      1 )
        self.assertEqual( palm.second,     2 )
        self.assertEqual( palm.third,      3 )
        self.assertEqual( palm.forth,      4 )
        self.assertEqual( palm.forth_item, 4 )

        self.assertEqual( palm.fifth, None ) # but
        with self.assertRaises(KeyError):
            palm['fifth']

        palm.clear()

        self.assertEqual( palm.first,      1 )
        self.assertEqual( palm.second,     2 )
        self.assertEqual( palm.third,      3 )
        self.assertEqual( palm.forth,      4 )
        self.assertEqual( palm.forth_item, 4 )

        self.assertEqual( palm.fifth, None ) # but
        with self.assertRaises(KeyError):
            palm['fifth']

    def test_clear_with_override(self):

        palm = PalmConfig(
            { 'first':  1, 'forth_item': 4 }, # managed
            { 'second': 2, 'sixth': None   }, # defaults
            rules=[
                PalmConfig.Rule( 'third',                   default=3 ),
                PalmConfig.Rule( 'forth', ['forth','item'], default=-4 )
                ],
            override=True
            )

        self.assertEqual( palm.override, True ) # can't override existing

        self.assertEqual( palm.first,      1 )
        self.assertEqual( palm.second,     2 )
        self.assertEqual( palm.third,      3 )
        self.assertEqual( palm.forth,      4 )
        self.assertEqual( palm.forth_item, 4 )

        self.assertEqual( palm.fifth, None ) # but
        with self.assertRaises(KeyError):
            palm['fifth']


        palm.clear()


        self.assertEqual( palm.first, None ) # but
        with self.assertRaises(KeyError):
            palm['first']

        self.assertEqual( palm.second,      2 )
        self.assertEqual( palm.third,       3 )
        self.assertEqual( palm.forth,      -4 )
        self.assertEqual( palm.forth_item, -4 )

        self.assertEqual( palm.fifth, None ) # but
        with self.assertRaises(KeyError):
            palm['fifth']

    def test_expand(self):

        palm = PalmConfig({
            'one':    1,
            'string': 'hello',
            'bool':   True,
            'none':   None,
            'deep':   '${deeper}',
            'deeper': '$string'
            })

        self.assertEqual(
            palm.expand( '$one/$$one/${string}/$bool/$none/$deep/$unknown' ),
            '1/$one/hello/True//hello/$unknown'
            )
        self.assertEqual(
            palm.expand(
                '$one/$$one/${string}/$bool/$none/$deep/$unknown',
                keep_unknown=False
                ),
            '1/$one/hello/True//hello/'
            )
        self.assertEqual(
            palm.expand(
                '$one/$$one/${string}/$bool/$none/$deep/$unknown',
                context={ 'one': 2, 'deep': '${deeper}' }
                ),
            '2/$one/${string}/$bool/$none/${deeper}/$unknown'
            )
        self.assertEqual(
            palm.expand(
                '$one/$$one/${string}/$bool/$none/$deep/$unknown',
                context={ 'one': 2, 'deep': '${deeper}' },
                keep_unknown=False
                ),
            '2/$one/////'
            )

    def test_eval(self):

        palm = PalmConfig({
            'number': 1234,
            'string': 'hello',
            'bool':   True,
            'none':   None,
            'deep':   '${deeper}',
            'deeper': '$number',
            })

        self.assertEqual( palm.eval('number'), 1234    )
        self.assertEqual( palm.eval('string'), 'hello' )
        self.assertEqual( palm.eval('bool'),   True    )
        self.assertEqual( palm.eval('none'),   None    )
        self.assertEqual( palm.eval('deep'),   1234    )

        self.assertEqual( palm.eval('unknown'), None   )

    def test_eval_other_context(self):

        palm = PalmConfig({
            'number': 1234,
            'string': 'hello',
            'bool':   True,
            'none':   None,
            'deep':   '${deeper}',
            'deeper': '$number',
            })

        self.assertEqual(
            palm.eval('deeper'),
            1234
            )
        self.assertEqual(
            palm.eval('deeper', context={'number': 123.456}),
            123.456
            )

        self.assertEqual(
            palm.eval('deep', context={'number': 123.456}),
            '${deeper}'
            )
        self.assertEqual(
            palm.eval('deep', context={'number': 123.456},keep_unknown=False),
            ''
            )

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