# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.

""" Test for bind command """

import re
import pytest

def in_tree(response, name, uclass, drv, depth, last_child):
    lines = [x.strip() for x in response.splitlines()]
    leaf = ''
    if depth != 0:
        leaf = '   ' + '    ' * (depth - 1)
        if not last_child:
            leaf = leaf + r'\|'
        else:
            leaf = leaf + '`'

    leaf = leaf + '-- ' + name
    line = (r' *{:10.10} *[0-9]*  \[ [ +] \]   {:20.20}  [` |]{}$'
            .format(uclass, drv, leaf))
    prog = re.compile(line)
    for l in lines:
        if prog.match(l):
            return True
    return False

@pytest.mark.boardspec('sandbox')
@pytest.mark.buildconfigspec('cmd_bind')
def test_bind_unbind_with_node(u_boot_console):

    tree = u_boot_console.run_command('dm tree')
    assert in_tree(tree, 'bind-test', 'simple_bus', 'simple_bus', 0, True)
    assert in_tree(tree, 'bind-test-child1', 'phy', 'phy_sandbox', 1, False)
    assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'simple_bus', 1, True)

    #bind usb_ether driver (which has no compatible) to usb@1 node.
    ##New entry usb_ether should appear in the dm tree
    response = u_boot_console.run_command('bind  /usb@1 usb_ether')
    assert response == ''
    tree = u_boot_console.run_command('dm tree')
    assert in_tree(tree, 'usb@1', 'ethernet', 'usb_ether', 1, True)

    #Unbind child #1. No error expected and all devices should be there except for bind-test-child1
    response = u_boot_console.run_command('unbind  /bind-test/bind-test-child1')
    assert response == ''
    tree = u_boot_console.run_command('dm tree')
    assert in_tree(tree, 'bind-test', 'simple_bus', 'simple_bus', 0, True)
    assert 'bind-test-child1' not in tree
    assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'simple_bus', 1, True)

    #bind child #1. No error expected and all devices should be there
    response = u_boot_console.run_command('bind  /bind-test/bind-test-child1 phy_sandbox')
    assert response == ''
    tree = u_boot_console.run_command('dm tree')
    assert in_tree(tree, 'bind-test', 'simple_bus', 'simple_bus', 0, True)
    assert in_tree(tree, 'bind-test-child1', 'phy', 'phy_sandbox', 1, True)
    assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'simple_bus', 1, False)

    #Unbind child #2. No error expected and all devices should be there except for bind-test-child2
    response = u_boot_console.run_command('unbind  /bind-test/bind-test-child2')
    assert response == ''
    tree = u_boot_console.run_command('dm tree')
    assert in_tree(tree, 'bind-test', 'simple_bus', 'simple_bus', 0, True)
    assert in_tree(tree, 'bind-test-child1', 'phy', 'phy_sandbox', 1, True)
    assert 'bind-test-child2' not in tree


    #Bind child #2. No error expected and all devices should be there
    response = u_boot_console.run_command('bind /bind-test/bind-test-child2 simple_bus')
    assert response == ''
    tree = u_boot_console.run_command('dm tree')
    assert in_tree(tree, 'bind-test', 'simple_bus', 'simple_bus', 0, True)
    assert in_tree(tree, 'bind-test-child1', 'phy', 'phy_sandbox', 1, False)
    assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'simple_bus', 1, True)

    #Unbind parent. No error expected. All devices should be removed and unbound
    response = u_boot_console.run_command('unbind  /bind-test')
    assert response == ''
    tree = u_boot_console.run_command('dm tree')
    assert 'bind-test' not in tree
    assert 'bind-test-child1' not in tree
    assert 'bind-test-child2' not in tree

    #try binding invalid node with valid driver
    response = u_boot_console.run_command('bind  /not-a-valid-node simple_bus')
    assert response != ''
    tree = u_boot_console.run_command('dm tree')
    assert 'not-a-valid-node' not in tree

    #try binding valid node with invalid driver
    response = u_boot_console.run_command('bind  /bind-test not_a_driver')
    assert response != ''
    tree = u_boot_console.run_command('dm tree')
    assert 'bind-test' not in tree

    #bind /bind-test. Device should come up as well as its children
    response = u_boot_console.run_command('bind  /bind-test simple_bus')
    assert response == ''
    tree = u_boot_console.run_command('dm tree')
    assert in_tree(tree, 'bind-test', 'simple_bus', 'simple_bus', 0, True)
    assert in_tree(tree, 'bind-test-child1', 'phy', 'phy_sandbox', 1, False)
    assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'simple_bus', 1, True)

    response = u_boot_console.run_command('unbind  /bind-test')
    assert response == ''

def get_next_line(tree, name):
    treelines = [x.strip() for x in tree.splitlines() if x.strip()]
    child_line = ''
    for idx, line in enumerate(treelines):
        if '-- ' + name in line:
            try:
                child_line = treelines[idx+1]
            except:
                pass
            break
    return child_line

@pytest.mark.boardspec('sandbox')
@pytest.mark.buildconfigspec('cmd_bind')
@pytest.mark.singlethread
def test_bind_unbind_with_uclass(u_boot_console):
    #bind /bind-test
    response = u_boot_console.run_command('bind  /bind-test simple_bus')
    assert response == ''

    #make sure bind-test-child2 is there and get its uclass/index pair
    tree = u_boot_console.run_command('dm tree')
    child2_line = [x.strip() for x in tree.splitlines() if '-- bind-test-child2' in x]
    assert len(child2_line) == 1

    child2_uclass = child2_line[0].split()[0]
    child2_index = int(child2_line[0].split()[1])

    #bind simple_bus as a child of bind-test-child2
    response = u_boot_console.run_command(
                    'bind  {} {} simple_bus'.format(child2_uclass, child2_index))

    #check that the child is there and its uclass/index pair is right
    tree = u_boot_console.run_command('dm tree')

    child_of_child2_line = get_next_line(tree, 'bind-test-child2')
    assert child_of_child2_line
    child_of_child2_index = int(child_of_child2_line.split()[1])
    assert in_tree(tree, 'simple_bus', 'simple_bus', 'simple_bus', 2, True)
    assert child_of_child2_index == child2_index + 1

    #unbind the child and check it has been removed
    response = u_boot_console.run_command('unbind  simple_bus {}'.format(child_of_child2_index))
    assert response == ''
    tree = u_boot_console.run_command('dm tree')
    assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'simple_bus', 1, True)
    assert not in_tree(tree, 'simple_bus', 'simple_bus', 'simple_bus', 2, True)
    child_of_child2_line = get_next_line(tree, 'bind-test-child2')
    assert child_of_child2_line == ''

    #bind simple_bus as a child of bind-test-child2
    response = u_boot_console.run_command(
                    'bind  {} {} simple_bus'.format(child2_uclass, child2_index))

    #check that the child is there and its uclass/index pair is right
    tree = u_boot_console.run_command('dm tree')
    treelines = [x.strip() for x in tree.splitlines() if x.strip()]

    child_of_child2_line = get_next_line(tree, 'bind-test-child2')
    assert child_of_child2_line
    child_of_child2_index = int(child_of_child2_line.split()[1])
    assert in_tree(tree, 'simple_bus', 'simple_bus', 'simple_bus', 2, True)
    assert child_of_child2_index == child2_index + 1

    #unbind the child and check it has been removed
    response = u_boot_console.run_command(
                    'unbind  {} {} simple_bus'.format(child2_uclass, child2_index))
    assert response == ''

    tree = u_boot_console.run_command('dm tree')
    assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'simple_bus', 1, True)

    child_of_child2_line = get_next_line(tree, 'bind-test-child2')
    assert child_of_child2_line == ''

    #unbind the child again and check it doesn't change the tree
    tree_old = u_boot_console.run_command('dm tree')
    response = u_boot_console.run_command(
                    'unbind  {} {} simple_bus'.format(child2_uclass, child2_index))
    tree_new = u_boot_console.run_command('dm tree')

    assert response == ''
    assert tree_old == tree_new

    response = u_boot_console.run_command('unbind  /bind-test')
    assert response == ''
