import os
import pytest
import tempfile

from mercurial import (
    config,
    error,
    pycompat,
    util,
)
import pytest

from tortoisehg.util import (
    hglib,
    wconfig,
)

import helpers

def setup_module():
    global _tempdir
    _tempdir = helpers.mktmpdir(__name__)


def newrconfig(vals=None):
    if vals is None:
        vals = {}
    c = config.config()
    for k, v in isinstance(vals, dict) and vals.items() or vals:
        sec, it = k.split(b'.', 1)
        c.set(sec, it, v)
    return c

def newwconfig(vals=None):
    if vals is None:
        vals = {}
    return wconfig.config(newrconfig(vals))

# see wconfig.writefile()
def written(c):
    dest = pycompat.io.StringIO()
    c.write(dest)
    return hglib.fromunicode(dest.getvalue())

def writetempfile(s):
    fd, path = tempfile.mkstemp(dir=_tempdir)
    os.write(fd, s)
    os.close(fd)
    return path

# Since hg 5.8 (a3dced4b7b04), config dict is no longer a plain
# {key: value} dict, but has metadata for each key.

def metadict(src):
    return {k: metavalue(v) for k, v in src.items()}

def metavalue(v):
    return (v, b'', 0)

def plaindict(src):
    return {k: plainvalue(v) for k, v in src.items()}

def plainvalue(v):
    value, source, level = v
    assert isinstance(source, bytes)
    assert isinstance(level, int)
    return value

def with_rconfig(f):
    f.__test__ = True
    return pytest.mark.usefixtures('rconfig')(f)

def with_wconfig(f):
    f.__test__ = True
    return pytest.mark.usefixtures('wconfig')(f)

def with_both(f):
    f.__test__ = True
    return pytest.mark.usefixtures('both')(f)


@pytest.fixture(name='wconfig')
def fixture_wconfig():
    if wconfig._hasiniparse:
        yield
    else:
        pytest.skip()

@pytest.fixture(name='rconfig')
def fixture_rconfig():
    orighasiniparse = wconfig._hasiniparse
    wconfig._hasiniparse = False
    try:
        yield
    finally:
        wconfig._hasiniparse = orighasiniparse

@pytest.fixture(name='both', params=['wconfig', 'rconfig'])
def fixture_both(request):
    request.getfixturevalue(request.param)


@with_both
def check_copy():
    c = newwconfig({b'foo.bar': b'baz'})
    assert c.copy().__class__ == c.__class__
    assert c.copy().get(b'foo', b'bar') == b'baz'

@with_both
def check_contains():
    c = newwconfig({b'foo.bar': b'baz'})
    assert b'foo' in c, c
    assert b'bar' not in c, c

@with_both
def check_getitem():
    c = newwconfig({b'foo.bar': b'x', b'foo.baz': b'y'})
    assert plaindict(c[b'foo']) == {b'bar': b'x', b'baz': b'y'}
    assert dict(c[b'unknown']) == {}

@with_both
def check_getitem_empty_then_set_no_effect():
    c = newwconfig()
    c[b'unknown'][b'bar'] = b'baz'
    assert not c.get(b'unknown', b'bar'), c.get(b'unknown', b'bar')

@with_both
def check_set_followed_by_getitem_empty():
    c = newwconfig()
    c[b'unknown']
    c.set(b'unknown', b'foo', b'bar')
    assert c.get(b'unknown', b'foo') == b'bar'
    assert plainvalue(c[b'unknown'][b'foo']) == b'bar'

@with_both
def check_dict_contains():
    c = newwconfig({b'foo.bar': b'x'})
    assert b'bar' in c[b'foo'], c[b'foo']
    assert b'baz' not in c[b'foo'], c[b'foo']

@with_both
def check_dict_getitem():
    c = newwconfig({b'foo.bar': b'x'})
    assert plainvalue(c[b'foo'][b'bar']) == b'x'
    with pytest.raises(KeyError):
        c[b'foo'][b'baz']

@with_both
def check_dict_setitem():
    c = newwconfig({b'foo.bar': b'x'})
    c[b'foo'][b'bar'] = metavalue(b'y')
    c[b'foo'][b'baz'] = metavalue(b'z')
    assert c[b'foo'][b'bar'] == metavalue(b'y')
    assert c[b'foo'][b'baz'] == metavalue(b'z')

@with_wconfig  # original config doesn't preserve the order
def check_dict_setitem_preserve_order():
    c = newwconfig([(b'foo.bar', b'x'), (b'foo.baz', b'y')])
    assert list(c[b'foo']) == [b'bar', b'baz']
    c[b'foo'][b'bar'] = metavalue(b'z')
    assert list(c[b'foo']) == [b'bar', b'baz']

@with_both
def check_dict_iter():
    c = newwconfig({b'foo.bar': b'x', b'foo.baz': b'y'})
    assert set(c[b'foo']) == {b'bar', b'baz'}

@with_both
def check_dict_len():
    c = newwconfig({b'foo.bar': b'x'})
    assert len(c[b'foo']) == 1

@with_both
def check_dict_update():
    c = newwconfig({b'foo.bar': b'x', b'foo.baz': b'y'})
    c[b'foo'].update(newwconfig({b'foo.bar': b'z', b'foo.baz': b'w'})[b'foo'])
    assert plainvalue(c[b'foo'][b'bar']) == b'z'
    assert plainvalue(c[b'foo'][b'baz']) == b'w'

@with_both
def check_dict_delitem():
    c = newwconfig({b'foo.bar': b'x'})
    del c[b'foo'][b'bar']
    assert b'bar' not in c[b'foo'], c[b'foo']

@with_both
def check_iter():
    c = newwconfig({b'foo.bar': b'x', b'baz.bax': b'y'})
    assert set(c) == {b'foo', b'baz'}

@with_both
def check_update():
    c0 = newwconfig({b'foo.bar': b'x', b'foo.blah': b'w'})
    c1 = newwconfig({b'foo.bar': b'y', b'baz.bax': b'z'})
    c0.update(c1)
    assert c0.get(b'foo', b'bar') == b'y'
    assert c0.get(b'baz', b'bax') == b'z'
    assert c0.get(b'foo', b'blah') == b'w'

@with_both
def check_get():
    c = newwconfig({b'foo.bar': b'baz'})
    assert c.get(b'foo', b'bar') == b'baz'
    assert c.get(b'foo', b'baz') == None
    assert c.get(b'foo', b'baz', b'x') == b'x'

@with_both
def check_source():
    c = newwconfig()
    c.set(b'foo', b'bar', b'baz', source=b'blah')
    assert c.source(b'foo', b'bar') == b'blah'

@with_both
def check_sections():
    c = newwconfig({b'foo.bar': b'x', b'baz.bax': b'y'})
    assert c.sections() == [b'baz', b'foo']

@with_both
def check_items():
    c = newwconfig({b'foo.bar': b'x', b'foo.baz': b'y'})
    assert dict(c.items(b'foo')) == {b'bar': b'x', b'baz': b'y'}

@with_both
def check_set():
    c = newwconfig({b'foo.bar': b'x'})
    c.set(b'foo', b'baz', b'y')
    c.set(b'foo', b'bar', b'w')
    c.set(b'newsection', b'bax', b'z')
    assert c.get(b'foo', b'baz') == b'y'
    assert c.get(b'foo', b'bar') == b'w'
    assert c.get(b'newsection', b'bax') == b'z'

@with_wconfig  # original config doesn't preserve the order
def check_set_preserve_order():
    c = newwconfig([(b'foo.bar', b'x'), (b'foo.baz', b'y')])
    assert list(c[b'foo']) == [b'bar', b'baz']
    c.set(b'foo', b'bar', b'z')
    assert list(c[b'foo']) == [b'bar', b'baz']

# TODO: test_parse
# TODO: test_read

@with_wconfig
def check_write_after_set():
    c = newwconfig()
    c.set(b'foo', b'bar', b'baz')
    assert written(c).rstrip() == b'[foo]\nbar = baz'

@with_wconfig
def check_write_empty():
    c = newwconfig()
    assert written(c).rstrip() == b''

@with_wconfig
def check_write_after_update():
    c = newwconfig()
    c.update(newwconfig({b'foo.bar': b'baz'}))
    assert written(c).rstrip() == b'[foo]\nbar = baz'

@with_wconfig
def check_read_write():
    c = newwconfig()
    s = b'[foo]\nbar = baz'
    c.read(path=b'foo', fp=util.bytesio(s))
    assert written(c).rstrip() == s

@with_wconfig
def check_read_write_missing_section_header_error():
    c = newwconfig()
    s = b'bar = baz'  # missing header
    c.read(path=b'foo', fp=util.bytesio(s))
    with pytest.raises(error.ParseError):
        c.write(util.bytesio())

@with_wconfig
def check_read_write_parsing_error():
    c = newwconfig()
    s = b'[foo]\n:bar = baz'  # Mercurial can parse it but INIConfig can't
    c.read(path=b'foo', fp=util.bytesio(s))
    with pytest.raises(error.ParseError):
        c.write(util.bytesio())

@with_wconfig
def check_write_after_dict_setitem():
    c = newwconfig({b'foo.bar': b'x'})
    c[b'foo'][b'bar'] = metavalue(b'y')
    assert written(c).rstrip() == b'[foo]\nbar = y'

@with_wconfig
def check_write_after_dict_update():
    c = newwconfig({b'foo.bar': b'x'})
    c[b'foo'].update(metadict({b'bar': b'y'}))
    assert written(c).rstrip() == b'[foo]\nbar = y'

@with_wconfig
def check_write_after_dict_delitem():
    c = newwconfig({b'foo.bar': b'x', b'foo.baz': b'y'})
    del c[b'foo'][b'bar']
    assert written(c).rstrip() == b'[foo]\nbaz = y'

@with_wconfig
def check_read_write_rem():
    c = newwconfig()
    s = b'[foo]\nrem = x'
    c.read(path=b'foo', fp=util.bytesio(s))
    c.set(b'foo', b'rem', b'y')
    assert written(c).rstrip() == b'[foo]\nrem = y'

@with_wconfig
def check_read_write_suboption():
    c = newwconfig()
    s = b'[foo]\nbar:baz = x'
    c.read(path=b'foo', fp=util.bytesio(s))
    c.set(b'foo', b'bar:baz', b'y')
    assert written(c).rstrip() == b'[foo]\nbar:baz = y'

@with_wconfig
def check_read_write_suboption_removal():
    c = newwconfig()
    s = b'[foo]\nbar:baz = x\nbar = y'
    c.read(path=b'foo', fp=util.bytesio(s))
    del c[b'foo'][b'bar:baz']
    assert written(c).rstrip() == b'[foo]\nbar = y'


@with_wconfig
def check_write_conflict_set_set():
    fname = writetempfile(b'[foo]\nbar = x')
    c0 = wconfig.readfile(fname)
    c1 = wconfig.readfile(fname)
    c1.set(b'foo', b'bar', b'y')
    wconfig.writefile(c1, fname)
    c0.set(b'foo', b'bar', b'z')
    wconfig.writefile(c0, fname)

    cr = wconfig.readfile(fname)
    assert cr.get(b'foo', b'bar') == b'z'

@with_wconfig
def check_write_conflict_del_set():
    fname = writetempfile(b'[foo]\nbar = x')
    c0 = wconfig.readfile(fname)
    c1 = wconfig.readfile(fname)
    del c1[b'foo'][b'bar']
    wconfig.writefile(c1, fname)
    c0.set(b'foo', b'bar', b'z')
    wconfig.writefile(c0, fname)

    cr = wconfig.readfile(fname)
    assert cr.get(b'foo', b'bar') == b'z'

@with_wconfig
def check_write_conflict_set_del():
    fname = writetempfile(b'[foo]\nbar = x')
    c0 = wconfig.readfile(fname)
    c1 = wconfig.readfile(fname)
    c1.set(b'foo', b'bar', b'y')
    wconfig.writefile(c1, fname)
    del c0[b'foo'][b'bar']
    wconfig.writefile(c0, fname)

    cr = wconfig.readfile(fname)
    assert not cr.get(b'foo', b'bar'), cr.get(b'foo', b'bar')

@with_wconfig
def check_write_conflict_del_del():
    fname = writetempfile(b'[foo]\nbar = x')
    c0 = wconfig.readfile(fname)
    c1 = wconfig.readfile(fname)
    del c1[b'foo'][b'bar']
    wconfig.writefile(c1, fname)
    del c0[b'foo'][b'bar']
    wconfig.writefile(c0, fname)  # shouldn't raise KeyError

    cr = wconfig.readfile(fname)
    assert not cr.get(b'foo', b'bar'), cr.get(b'foo', b'bar')

@with_wconfig
def check_write_noconflict_set_set():
    fname = writetempfile(b'[foo]\nbar = x')
    c0 = wconfig.readfile(fname)
    c1 = wconfig.readfile(fname)
    c1.set(b'foo', b'baz', b'y')
    wconfig.writefile(c1, fname)
    c0.set(b'foo', b'bar', b'z')
    wconfig.writefile(c0, fname)  # should not override foo.baz = y

    cr = wconfig.readfile(fname)
    assert cr.get(b'foo', b'bar') == b'z'
    assert cr.get(b'foo', b'baz') == b'y'
    # don't reload c1's change implicitly
    assert not c0.get(b'foo', b'baz'), c0.get(b'foo', b'baz')

@with_wconfig
def check_write_noconflict_del():
    fname = writetempfile(b'[foo]\nbar = x')
    c0 = wconfig.readfile(fname)
    c1 = wconfig.readfile(fname)
    del c1[b'foo'][b'bar']
    wconfig.writefile(c1, fname)
    wconfig.writefile(c0, fname)  # shouldn't override del foo.bar

    cr = wconfig.readfile(fname)
    assert not cr.get(b'foo', b'bar'), cr.get(b'foo', b'bar')
    # don't reload c1's change implicitly
    assert c0.get(b'foo', b'bar'), c0.get(b'foo', b'bar')


@with_wconfig
def check_write_copied():
    fname = writetempfile(b'[foo]\nbar = x')
    c0 = wconfig.readfile(fname)
    c1 = c0.copy()
    c1.set(b'foo', b'baz', b'y')
    wconfig.writefile(c1, fname)

    cr = wconfig.readfile(fname)
    assert cr.get(b'foo', b'bar') == b'x'
    assert cr.get(b'foo', b'baz') == b'y'

@with_wconfig
def check_write_copied_conflict():
    fname = writetempfile(b'[foo]\nbar = x')
    c0 = wconfig.readfile(fname)
    c1 = c0.copy()
    c0.set(b'foo', b'bar', b'y')
    wconfig.writefile(c0, fname)
    wconfig.writefile(c1, fname)  # shouldn't override foo.bar = y

    cr = wconfig.readfile(fname)
    assert cr.get(b'foo', b'bar') == b'y'

@with_wconfig
def check_write_copied_rconfig():
    c0 = newrconfig({b'foo.bar': b'x'})
    c1 = wconfig.config(c0)
    assert written(c1).rstrip() == b'[foo]\nbar = x'

@with_both
def check_readfile():
    fname = writetempfile(b'[foo]\nbar = baz')
    c = wconfig.readfile(fname)
    assert c.get(b'foo', b'bar') == b'baz'

@with_wconfig
def check_writefile():
    c = newwconfig({b'foo.bar': b'baz'})
    fname = writetempfile(b'')
    wconfig.writefile(c, fname)

    expected = b'[foo]%sbar = baz' % os.linesep.encode('ascii')
    with open(fname, 'rb') as fp:
        assert fp.read().rstrip() == expected
