# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.
#
# Authors: Florian Boucault <florian@fluendo.com>
#          Benjamin Kampmann <benjamin@fluendo.com>


"""
Utility functions for sorting
"""

import re
from elisa.core.utils.cancellable_defer import cancellable_coiterate


"""
As proved here http://blog.pobblelabs.org/2007/12/11/exception-handling-slow/
try-except is to time consuming, a Much faster way it the check for the digit
directly:
"""

NUMS = re.compile('([0-9]+)')

def natural_cmp(this, other):
    """
    Compare the two strings C{this} and C{other} with a more natural way.
    """
    return cmp(_natural_key(this), _natural_key(other))

def _natural_key(s):
    # strip the spaces
    s = s.strip()
    return [ part.isdigit() and int(part) or part.lower() for part in NUMS.split(s) ]

def natural_sort(l):
    """
    Case insensitive human compliant sort.

    @param l: list that needs to be sorted
    @type l:  list of unicode
    """
    l.sort(key=_natural_key)

def async_ordered_placement(sorted_list, sort_obj, compare=cmp):
    """
    Place the C{sort_obj} at the right position of the C{sorted_list}. As the
    name already says C{sorted_list} is meant to be a sorted list so that the
    new object can be placed at right position by comparing it with the others
    in the list.

    This is done in non-blocking manner.
    
    This method is returing a deferred that lets you cancel the operation when
    it is not finished yet.

    @param sorted_list: the reference list (sorted) the object should placed into
    @type sorted_list : C{list}
    @param sort_obj:    the (compareable) object you want to have placed in the
                        reference list
    @keyword compare:   method that should be used to compare the two objects.
                        the expected return values should be as for L{cmp}.
                        Default: L{cmp}
    @type compare:      C{Callable} like L{cmp}
    @rtype:             L{elisa.core.utils.cancellable_defer.CancellableDeferred}
    """
    def iterate(sorted_list, sort_obj):
        index = len(sorted_list)
        for x, ref_obj in enumerate(sorted_list):
            if compare(sort_obj, ref_obj) <= 0:
                index = x
                break
            yield None
        sorted_list.insert(index, sort_obj)

    return cancellable_coiterate(iterate, sorted_list, sort_obj)

def async_sorted_merge(reference_list, to_insert_list, compare=cmp):
    """
    Merge C{to_insert_list} into C{reference_list} by sorting every item into it
    in ansynchronous manner. The C{reference_list} needs to be sorted while the
    C{to_insert_list} is not expected to be so.

    @keyword compare:   method that should be used to compare the two objects.
                        the expected return values should be as for L{cmp}.
                        Default: L{cmp}
    @type compare:      C{Callable} like L{cmp}
    
    FIXME: this might not be sorting totally correctly if it is called twice at
    the same time with two different lists.
    """
    def iterate(reference, objects):
        for obj in objects:
            dfr = async_ordered_placement(reference, obj, compare)
            yield dfr
    return cancellable_coiterate(iterate, reference_list, to_insert_list)
