import copy


def rename(original, names, indicies=None):
    """
    Rename sample names flexibly.

    Parameters
    ----------
    original : list
        List of original names.
    names : dict or list
        Dict of old names to new names or list of new names.
    indicies : list or tuple, optional
        List of 0-based sample indicies. Alternatively, a tuple
        (int, int) can be used to specify an index range.

    Returns
    -------
    list
        List of updated names.

    Examples
    --------

    >>> from fuc import common
    >>> original = ['A', 'B', 'C', 'D']
    >>> common.rename(original, ['1', '2', '3', '4'])
    ['1', '2', '3', '4']
    >>> common.rename(original, {'B': '2', 'C': '3'})
    ['A', '2', '3', 'D']
    >>> common.rename(original, ['2', '4'], indicies=[1, 3])
    ['A', '2', 'C', '4']
    >>> common.rename(original, ['2', '3'], indicies=(1, 3))
    ['A', '2', '3', 'D']
    """
    samples = copy.deepcopy(original)

    if not isinstance(names, list) and not isinstance(names, dict):
        raise TypeError("Argument 'names' must be dict or list.")

    if len(names) > len(samples):
        raise ValueError("There are too many names.")

    if isinstance(names, list) and indicies is not None:
        if isinstance(indicies, tuple):
            if len(indicies) != 2:
                raise ValueError("Index range must be two integers.")
            l = len(range(indicies[0], indicies[1]))
        elif isinstance(indicies, list):
            l = len(indicies)
        else:
            raise TypeError("Argument 'indicies' must be list or tuple.")

        if len(names) != l:
            raise ValueError("Names and indicies have different lengths.")

    if isinstance(names, list):
        if len(names) == len(samples):
            names = dict(zip(samples, names))
        else:
            if indicies is None:
                message = ("There are too few names. If this was "
                    "intended, use the 'indicies' argument.")
                raise ValueError(message)
            elif isinstance(indicies, tuple):
                names = dict(zip(samples[indicies[0]:indicies[1]], names))
            else:
                names = dict(zip([samples[i] for i in indicies], names))

    for old, new in names.items():
        i = samples.index(old)
        samples[i] = new

    if len(samples) > len(set(samples)):
        raise ValueError('There are more than one duplicate names.')

    return samples