#!/usr/bin/python

# Copyright (c) 2017 Cisco and/or its affiliates.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""This script renames the given robot keywords in the given directory
recursively.

Example:

  ./rename_robot_keywords.py -i kws.csv -s ";" -d ~/ws/vpp/git/csit/ -vvv

  Input file "kws.csv" is CSV file exported from e.g. MS Excel. Its structure
  must be:

    <Old keyword name><separator><New keyword name>

  One keyword per line.

"""

import argparse
import sys
import re
from os import walk, rename
from os.path import join


def time_interval(func):
    """Decorator function to measure the time spent by the decorated function.

    :param func: Decorated function.
    :type func: Callable object.
    :returns: Wrapper function.
    :rtype: Callable object.
    """

    import time

    def wrapper(*args, **kwargs):
        start = time.clock()
        result = func(*args, **kwargs)
        stop = time.clock()
        print("\nRenaming done in {:.5g} seconds\n".
              format(stop - start))
        return result
    return wrapper


def get_files(path, extension):
    """Generates the list of files to process.

    :param path: Path to files.
    :param extension: Extension of files to process. If it is the empty string,
    all files will be processed.
    :type path: str
    :type extension: str
    :returns: List of files to process.
    :rtype: list
    """

    file_list = list()
    for root, dirs, files in walk(path):
        for filename in files:
            if extension:
                if filename.endswith(extension):
                    file_list.append(join(root, filename))
            else:
                file_list.append(join(root, filename))

    return file_list


def read_keywords(args):
    """This function reads the keywords from the input file and creates:

    - a dictionary where the key is the old name and the value is the new name,
      these keywords will be further processed.
    - a list of keywords which will not be processed, typically keywords with
    argument(s) in its names.
    - a list of duplicates - duplicated keyword names or names which are parts
    of another keyword name, they will not be processed.

    :param args:  Parsed arguments.
    :type args: ArgumentParser
    :returns: keyword names - dictionary where the key is the old name and the
    value is the new name; ignored keyword names - list of keywords which will
    not be processed; duplicates - duplicated keyword names or names which are
    parts of another keyword name, they will not be processed.
    :rtype: tuple(dict, list, list)
    """

    kw_names = dict()
    ignored_kw_names = list()
    duplicates = list()

    for line in args.input:
        old_name, new_name = line.split(args.separator)
        if '$' in old_name:
            ignored_kw_names.append((old_name, new_name[:-1]))
        elif old_name in kw_names.keys():
            duplicates.append((old_name, new_name[:-1]))
        else:
            kw_names[old_name] = new_name[:-1]

    # Remove duplicates:
    for old_name, _ in duplicates:
        new_name = kw_names.pop(old_name, None)
        if new_name:
            duplicates.append((old_name, new_name))

    # Find KW names which are parts of other KW names:
    for old_name in kw_names.keys():
        count = 0
        for key in kw_names.keys():
            if old_name in key:
                count += 1
            if old_name in kw_names[key]:
                if old_name != key:
                    count += 1
        if count > 1:
            duplicates.append((old_name, kw_names[old_name]))
            kw_names.pop(old_name)

    return kw_names, ignored_kw_names, duplicates


def rename_keywords(file_list, kw_names, args):
    """Rename the keywords in specified files.

    :param file_list: List of files to be processed.
    :param kw_names: Dictionary  where the key is the old name and the value is
    the new name
    :type file_list: list
    :type kw_names: dict
    """

    kw_not_found = list()

    for old_name, new_name in kw_names.items():
        kw_found = False
        if args.verbosity > 0:
            print("\nFrom: {}\n  To: {}\n".format(old_name, new_name))
        for file_name in file_list:
            tmp_file_name = file_name + ".new"
            with open(file_name) as file_read:
                file_write = open(tmp_file_name, 'w')
                occurrences = 0
                for line in file_read:
                    new_line = re.sub(old_name, new_name, line)
                    file_write.write(new_line)
                    if new_line != line:
                        occurrences += 1
                if occurrences:
                    kw_found = True
                    if args.verbosity > 1:
                        print(" {:3d}: {}".format(occurrences, file_name))
                file_write.close()
            rename(tmp_file_name, file_name)
        if not kw_found:
            kw_not_found.append(old_name)

    if args.verbosity > 0:
        print("\nKeywords not found:")
        for item in kw_not_found:
            print("  {}".format(item))


def parse_args():
    """Parse arguments from command line.

    :returns: Parsed arguments.
    :rtype: ArgumentParser
    """

    parser = argparse.ArgumentParser(description=__doc__,
                                     formatter_class=argparse.
                                     RawDescriptionHelpFormatter)
    parser.add_argument("-i", "--input",
                        required=True,
                        type=argparse.FileType('r'),
                        help="Text file with the old keyword name and the new "
                             "keyword name separated by separator per line.")
    parser.add_argument("-s", "--separator",
                        default=";",
                        type=str,
                        help="Separator which separates the old and the new "
                             "keyword name.")
    parser.add_argument("-d", "--dir",
                        required=True,
                        type=str,
                        help="Directory with robot files where the keywords "
                             "should be recursively searched.")
    parser.add_argument("-v", "--verbosity", action="count",
                        help="Set the output verbosity.")
    return parser.parse_args()


@time_interval
def main():
    """Main function."""

    args = parse_args()

    kw_names, ignored_kw_names, duplicates = read_keywords(args)

    file_list = get_files(args.dir, "robot")

    if args.verbosity > 2:
        print("\nList of files to be processed:")
        for item in file_list:
            print("  {}".format(item))
        print("\n{} files to be processed.\n".format(len(file_list)))

        print("\nList of keywords to be renamed:")
        for item in kw_names:
            print("  {}".format(item))
        print("\n{} keywords to be renamed.\n".format(len(kw_names)))

    rename_keywords(file_list, kw_names, args)

    if args.verbosity >= 0:
        print("\nIgnored keywords: ({})".format(len(ignored_kw_names)))
        for old, new in ignored_kw_names:
            print("  From: {}\n    To: {}\n".format(old, new))

        print("\nIgnored duplicates ({}):".format(len(duplicates)))
        for old, new in duplicates:
            print("  From: {}\n    To: {}\n".format(old, new))


if __name__ == "__main__":
    sys.exit(main())