#!/usr/bin/env python
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Print a diff generated by generate_idl_diff.py.
Before printing, sort the diff in the alphabetical order or the order of
diffing tags.
Usage: print_idl_diff.py diff_file.json order
    diff.json:
        Output of generate_idl_diff.py. The json file contains a dictionary
        that represents a diff between two different Chromium versions. The
        structure of the dictionary is like below.
    order:
        Specify how to sort. Either by "ALPHABET" or "TAG".
"""

from collections import OrderedDict
import sys

from webkitpy.bindings.generate_idl_diff import load_json_file
from webkitpy.bindings.generate_idl_diff import EXTATTRIBUTES_AND_MEMBER_TYPES
from webkitpy.bindings.generate_idl_diff import DIFF_TAG
from webkitpy.bindings.generate_idl_diff import DIFF_TAG_ADDED
from webkitpy.bindings.generate_idl_diff import DIFF_TAG_DELETED


# pylint: disable=W0105
"""Refer to the explanation of generate_idl_diff.py's input files.
The deffference between the input structure of generate_idl_diff.py and
that of print_diff.py is whether diffing tags are included or not.
    {'Interface': {
            'diff_tag': 'deleted'
            'ExtAttributes': [{'Name': '...'
                               'diff_tag': 'deleted'},
                               ...,
                             ],
            'Consts': [{'Type': '...',
                        'Name': '...',
                        'Value': '...'
                        'diff_tag': 'deleted'},
                        ...,
                      ],
            'Attributes': [{'Type': '...',
                            'Name': '...',
                            'ExtAttributes':[{'Name': '...'},
                                              ...,
                                            ]
                            'diff_tag': 'deleted'},
                            ...,
                          ],
            'Operations': [{'Type': '...',
                            'Name': '...',
                            'ExtAttributes':[{'Name': '...'},
                                              ...,
                                            ],
                            'Arguments': [{'Type': '...',
                                           'Name': '...'},
                                           ...,
                                         ]
                            'diff_tag': 'deleted'},
                            ...,
                          ],
            'Name': '...'
        },
        {
            'ExtAttributes': [{'Name': '...'},
                               ...,
                             ],
            'Consts': [{'Type': '...',
                        'Name': '...',
                        'Value': '...'
                        'diff_tag': 'added'},
                        ...,
                      ],
            'Attributes': [{'Type': '...',
                            'Name': '...',
                            'ExtAttributes':[{'Name': '...'},
                                              ...,
                                            ]},
                            ...,
                          ],
            'Operations': [{'Type': '...',
                            'Name': '...',
                            'ExtAttributes':[{'Name': '...'},
                                              ...,
                                            ],
                            'Arguments': [{'Type': '...',
                                           'Name': '...'},
                                           ...,
                                         ]
                            'diff_tag': 'deleted'},
                            ...,
                           ],
            'Name': '...'
        },
        ...,
    }
"""


class Colorize(object):
    """This class outputs a colored text to sys.stdout.
    TODO(bashi): This class doesn't work on Windows. Provide a way to suppress
    escape sequences.
    """

    BLACK = 30
    RED = 31
    GREEN = 32
    YELLOW = 33
    COLORS = (BLACK, RED, GREEN, YELLOW)

    def __init__(self, out):
        self.out = out

    def reset_color(self):
        """Reset text's color to default."""
        self.out.write('\033[0m')

    def change_color(self, color):
        """Change text's color by specifing arguments.
            Args:
                color: A new color to change. It should be one of |COLORS|.
        """
        if color in self.COLORS:
            self.out.write('\033[' + str(color) + 'm')
        else:
            raise Exception('Unsupported color.')

    def writeln(self, string):
        """Print text with a line-break."""
        self.out.write(string + '\n')

    def write(self, string):
        """Print text without a line-break."""
        self.out.write(string)


def sort_member_types(interface):
    """Sort the members in the order of EXTATTRIBUTES_AND_MEMBER_TYPES.
    Args:
        interface: An "interface" object
    Returns:
        A sorted "interface" object
    """
    sorted_interface = OrderedDict()
    for member_type in EXTATTRIBUTES_AND_MEMBER_TYPES:
        sorted_interface[member_type] = interface.get(member_type)
    sorted_interface[DIFF_TAG] = interface.get(DIFF_TAG)
    return sorted_interface


def group_by_tag(interface_or_member_list):
    """Group members of |interface_or_member_list| by tags.
    Args:
        interface_or_member_list: A list of interface names or a list of "members"
    Returns:
        A tuple of (removed, added, unchanged) where
        removed: A list of removed members
        added: A list of added members
        unspecified: A list of other members
    """
    removed = []
    added = []
    unspecified = []
    for interface_or_member in interface_or_member_list:
        if DIFF_TAG in interface_or_member:
            if interface_or_member[DIFF_TAG] == DIFF_TAG_DELETED:
                removed.append(interface_or_member)
            elif interface_or_member[DIFF_TAG] == DIFF_TAG_ADDED:
                added.append(interface_or_member)
        else:
            unspecified.append(interface_or_member)
    return (removed, added, unspecified)


def sort_interface_names_by_tags(interfaces):
    """Sort interface names as follows.
    [names of deleted "interface"s
    -> names of added "interface"s
    -> names of other "interface"s]
    Args:
        interfaces: "interface" objects.
    Returns:
        A list of sorted interface names
    """
    interface_list = interfaces.values()
    removed, added, unspecified = group_by_tag(interface_list)
    # pylint: disable=W0110
    removed = map(lambda interface: interface['Name'], removed)
    # pylint: disable=W0110
    added = map(lambda interface: interface['Name'], added)
    # pylint: disable=W0110
    unspecified = map(lambda interface: interface['Name'], unspecified)
    sorted_interface_names = removed + added + unspecified
    return sorted_interface_names


def sort_members_by_tags(interface):
    """Sort members of a given interface in the order of diffing tags.
    Args:
        An "interface" object
    Returns:
        A sorted "interface" object
    """
    sorted_interface = OrderedDict()
    if DIFF_TAG in interface:
        return interface
    for member_type in EXTATTRIBUTES_AND_MEMBER_TYPES:
        member_list = interface[member_type]
        removed, added, unspecified = group_by_tag(member_list)
        sorted_interface[member_type] = removed + added + unspecified
    return sorted_interface


def sort_diff_by_tags(interfaces):
    """Sort an "interfaces" object in the order of diffing tags.
    Args:
        An "interfaces" object loaded by load_json_data().
    Returns:
        A sorted "interfaces" object
    """
    sorted_interfaces = OrderedDict()
    sorted_interface_names = sort_interface_names_by_tags(interfaces)
    for interface_name in sorted_interface_names:
        interface = sort_members_by_tags(interfaces[interface_name])
        sorted_interfaces[interface_name] = sort_member_types(interface)
    return sorted_interfaces


def sort_members_in_alphabetical_order(interface):
    """Sort a "members" object in the alphabetical order.
    Args:
        An "interface" object
    Returns:
        A sorted "interface" object
    """
    sorted_interface = OrderedDict()
    for member_type in EXTATTRIBUTES_AND_MEMBER_TYPES:
        sorted_members = sorted(interface[member_type],
                                key=lambda member: member['Name'])
        sorted_interface[member_type] = sorted_members
    return sorted_interface


def sort_diff_in_alphabetical_order(interfaces):
    """Sort an "interfaces" object in the alphabetical order.
    Args:
        An "interfaces" object.
    Returns:
        A sorted "interfaces" object
    """
    sorted_interfaces = OrderedDict()
    for interface_name in sorted(interfaces.keys()):
        interface = interfaces[interface_name]
        sorted_interface = sort_members_in_alphabetical_order(interface)
        sorted_interface[DIFF_TAG] = interface.get(DIFF_TAG)
        sorted_interfaces[interface_name] = sorted_interface
    return sorted_interfaces


def print_member_with_color(member, out):
    """Print the "member" with a colored text. '+' is added to an added
    "member". '-' is added to a removed "member".
    Args:
        member: A "member" object
    """
    if DIFF_TAG in member:
        if member[DIFF_TAG] == DIFF_TAG_DELETED:
            out.change_color(Colorize.RED)
            out.write('- ')
        elif member[DIFF_TAG] == DIFF_TAG_ADDED:
            out.change_color(Colorize.GREEN)
            out.write('+ ')
    else:
        out.change_color(Colorize.BLACK)
        out.write('  ')


def print_extattributes(extattributes, out):
    """Print extattributes in an "interface" object.
    Args:
        A list of "ExtAttributes" in the "interface" object
    """
    for extattribute in extattributes:
        out.write('    ')
        print_member_with_color(extattribute, out)
        out.writeln(extattribute['Name'])


def print_consts(consts, out):
    """Print consts in an "interface" object.
    Args:
        A list of "Consts" of the "interface" object
    """
    for const in consts:
        out.write('    ')
        print_member_with_color(const, out)
        out.write(str(const['Type']))
        out.write(' ')
        out.write(const['Name'])
        out.write(' ')
        out.writeln(const['Value'])


def print_items(items, callback, out):
    """Calls |callback| for each item in |items|, printing commas between
    |callback| calls.
    Args:
        items: extattributes or arguments
    """
    count = 0
    for item in items:
        callback(item)
        count += 1
        if count < len(items):
            out.write(', ')


def print_extattributes_in_member(extattributes, out):
    """Print extattributes in a "member" object.
    Args:
        A list of "ExtAttributes" in the "member" object
    """
    def callback(extattribute):
        out.write(extattribute['Name'])

    out.write('[')
    print_items(extattributes, callback, out)
    out.write(']')


def print_attributes(attributes, out):
    """Print attributes in an "interface" object.
    Args:
        A list of "Attributes" in the "interface" object
    """
    for attribute in attributes:
        out.write('    ')
        print_member_with_color(attribute, out)
        if attribute['ExtAttributes']:
            print_extattributes_in_member(attribute['ExtAttributes'], out)
        out.write(str(attribute['Type']))
        out.write(' ')
        out.writeln(attribute['Name'])


def print_arguments(arguments, out):
    """Print arguments in a "members" object named "Operations".
    Args: A list of "Arguments"
    """
    def callback(argument):
        out.write(argument['Name'])

    out.write('(')
    print_items(arguments, callback, out)
    out.writeln(')')


def print_operations(operations, out):
    """Print operations in a "member" object.
    Args:
        A list of "Operations"
    """
    for operation in operations:
        out.write('    ')
        print_member_with_color(operation, out)
        if operation['ExtAttributes']:
            print_extattributes_in_member(operation['ExtAttributes'], out)
        out.write(str(operation['Type']))
        out.write(' ')
        if operation['Arguments']:
            out.write(operation['Name'])
            print_arguments(operation['Arguments'], out)
        else:
            out.writeln(operation['Name'])


def print_diff(diff, out):
    """Print the diff on a shell.
    Args:
        A sorted diff
    """
    for interface_name, interface in diff.iteritems():
        print_member_with_color(interface, out)
        out.change_color(Colorize.YELLOW)
        out.write('[[')
        out.write(interface_name)
        out.writeln(']]')
        out.reset_color()
        for member_name, member in interface.iteritems():
            if member_name == 'ExtAttributes':
                out.writeln('ExtAttributes')
                print_extattributes(member, out)
            elif member_name == 'Consts':
                out.writeln('  Consts')
                print_consts(member, out)
            elif member_name == 'Attributes':
                out.writeln('  Attributes')
                print_attributes(member, out)
            elif member_name == 'Operations':
                out.writeln('  Operations')
                print_operations(member, out)
            out.reset_color()


def print_usage():
    """Show usage."""
    sys.stdout.write('Usage: print_diff.py <diff_file.json> <"TAG"|"ALPHABET">\n')


def main(argv):
    if len(argv) != 2:
        print_usage()
        exit(1)
    json_data = argv[0]
    order = argv[1]
    diff = load_json_file(json_data)
    if order == 'TAG':
        sort_func = sort_diff_by_tags
    elif order == 'ALPHABET':
        sort_func = sort_diff_in_alphabetical_order
    else:
        print_usage()
        exit(1)
    sorted_diff = sort_func(diff)
    out = Colorize(sys.stdout)
    print_diff(sorted_diff, out)


if __name__ == '__main__':
    main(sys.argv[1:])
