from testutils import assert_raises

#test only makes sense with python 3.8 or higher (or RustPython)
import sys
import platform
if platform.python_implementation() == 'CPython':
    assert sys.version_info >= (3, 8), 'Incompatible Python Version, expected CPython 3.8 or later'
elif platform.python_implementation == 'RustPython':
    # ok
    pass
else:
    # other implementation - lets give it a try
    pass


# lets start tersing
foo = 'bar'

assert f"{''}" == ''
assert f"{f'{foo}'}" == 'bar'
assert f"foo{foo}" == 'foobar'
assert f"{foo}foo" == 'barfoo'
assert f"foo{foo}foo" == 'foobarfoo'
assert f"{{foo}}" == '{foo}'
assert f"{ {foo} }" == "{'bar'}"
assert f"{f'{{}}'}" == '{}' # don't include escaped braces in nested f-strings
assert f'{f"{{"}' == '{'
assert f'{f"}}"}' == '}'
assert f'{foo}' f"{foo}" 'foo' == 'barbarfoo'
assert f'{"!:"}' == '!:'
assert fr'x={4*10}\n' == 'x=40\\n'
assert f'{16:0>+#10x}' == '00000+0x10'
assert f"{{{(lambda x: f'hello, {x}')('world}')}" == '{hello, world}'
assert f"""{f'''{f"{f'{foo}'}"}'''}""" == 'bar'
assert f"""{1:{1}{f'n'}}""" == '1'
assert f'{ {1}.pop()}' == '1'
assert f"""{1:{{1}.pop()}{f'n'}}""" == '1'


# base test of self documenting strings
assert f'{foo=}' == "foo='bar'" # TODO ' missing

num=42

f'{num=}' # keep this line as it will fail when using a python 3.7 interpreter 

assert f'{num=}' == 'num=42'
assert f'{num=:>10}' == 'num=        42'


spec = "0>+#10x"
assert f"{16:{spec}}{foo}" == '00000+0x10bar'

part_spec = ">+#10x"
assert f"{16:0{part_spec}}{foo}" == '00000+0x10bar'

# TODO: RUSTPYTHON, delete the next block once `test_fstring.py` can successfully parse
assert f'{10:#{1}0x}' == '       0xa'
assert f'{10:{"#"}1{0}{"x"}}' == '       0xa'
assert f'{-10:-{"#"}1{0}x}' == '      -0xa'
assert f'{-10:{"-"}#{1}0{"x"}}' == '      -0xa'

spec = "bla"
assert_raises(ValueError, lambda: f"{16:{spec}}")

# Normally `!` cannot appear outside of delimiters in the expression but
# cpython makes an exception for `!=`, so we should too.

assert f'{1 != 2}' == 'True'


# conversion flags

class Value:
    def __format__(self, spec):
        return "foo"

    def __repr__(self):
        return "bar"

    def __str__(self):
        return "baz"

v = Value()

assert f'{v}' == 'foo'
assert f'{v!r}' == 'bar'
assert f'{v!s}' == 'baz'
assert f'{v!a}' == 'bar'

# advanced expressions:

assert f'{True or True}' == 'True'
assert f'{1 == 1}' == 'True'
assert f'{"0" if True else "1"}' == '0'

# Test ascii representation of unicodes:
v = "\u262e"
assert f'>{v}' == '>\u262e'
assert f'>{v!r}' == ">'\u262e'"
assert f'>{v!s}' == '>\u262e'
assert f'>{v!a}' == r">'\u262e'"


# Test format specifier after conversion flag
assert f'{"42"!s:<5}' == '42   ', '#' + f'{"42"!s:5}' +'#'

assert f'{"42"!s:<5}' == '42   ', '#' + f'{"42"!s:<5}' +'#'
assert f'{"42"!s:>5}' == '   42', '#' + f'{"42"!s:>5}' +'#'

assert f'{"42"=!s:5}' == '"42"=42   ', '#'+ f'{"42"=!s:5}' +'#'
assert f'{"42"=!s:<5}' == '"42"=42   ', '#'+ f'{"42"=!s:<5}' +'#'
assert f'{"42"=!s:>5}' == '"42"=   42', '#'+ f'{"42"=!s:>5}' +'#'


### Tests for fstring self documenting form CPython

class C:
    def assertEqual(self, a,b):
        assert a==b, "{0} == {1}".format(a,b)

self=C()

x = 'A string'
self.assertEqual(f'{10=}', '10=10')
self.assertEqual(f'{x=}', 'x=' + repr(x))
self.assertEqual(f'{x =}', 'x =' + repr(x))
self.assertEqual(f'{x=!s}', 'x=' + str(x))
self.assertEqual(f'{x=!r}', 'x=' + repr(x))
self.assertEqual(f'{x=!a}', 'x=' + ascii(x))

x = 2.71828
self.assertEqual(f'{x=:.2f}', 'x=' + format(x, '.2f'))
self.assertEqual(f'{x=:}', 'x=' + format(x, ''))
self.assertEqual(f'{x=!r:^20}', 'x=' + format(repr(x), '^20'))
self.assertEqual(f'{x=!s:^20}', 'x=' + format(str(x), '^20'))
self.assertEqual(f'{x=!a:^20}', 'x=' + format(ascii(x), '^20'))

x = 9
self.assertEqual(f'{3*x+15=}', '3*x+15=42')

# There is code in ast.c that deals with non-ascii expression values.  So,
# use a unicode identifier to trigger that.
tenπ = 31.4
self.assertEqual(f'{tenπ=:.2f}', 'tenπ=31.40')

# Also test with Unicode in non-identifiers.
#self.assertEqual(f'{"Σ"=}', '"Σ"=\'Σ\'') ' TODO ' missing

# Make sure nested fstrings still work.
self.assertEqual(f'{f"{3.1415=:.1f}":*^20}', '*****3.1415=3.1*****')

# Make sure text before and after an expression with = works
# correctly.
pi = 'π'
#self.assertEqual(f'alpha α {pi=} ω omega', "alpha α pi='π' ω omega") # ' missing around pi

# Check multi-line expressions.
#self.assertEqual(f'''{3=}''', '\n3\n=3') # TODO: multiline f strings not supported, seems to be an rustpython issue

# Since = is handled specially, make sure all existing uses of
# it still work.

self.assertEqual(f'{0==1}', 'False')
self.assertEqual(f'{0!=1}', 'True')
self.assertEqual(f'{0<=1}', 'True')
self.assertEqual(f'{0>=1}', 'False')

# Make sure leading and following text works.
x = 'foo'
self.assertEqual(f'X{x=}Y', 'Xx='+repr(x)+'Y')

# Make sure whitespace around the = works.
self.assertEqual(f'X{x  =}Y', 'Xx  ='+repr(x)+'Y')
self.assertEqual(f'X{x=  }Y', 'Xx=  '+repr(x)+'Y')
self.assertEqual(f'X{x  =  }Y', 'Xx  =  '+repr(x)+'Y')

# self.assertEqual(f'X{x  =}Y', 'Xx  ='+x+'Y')
# self.assertEqual(f'X{x=  }Y', 'Xx=  '+x+'Y')
# self.assertEqual(f'X{x  =  }Y', 'Xx  =  '+x+'Y')
