# -*- coding: utf-8 -*-

import __builtin__
import os
import locale
import sys
import math
import re
import time
import datetime
import mako.template
import pkg_resources
import xml
from xml.dom.minidom import parse, parseString
#TODO fix this import
#from scroll.utils import pad_box

RANGE_SEPARATOR = '_'
GROUP_SEPARATOR = '-'
GROUP_RANGE_SEPARATOR = ','
SVG_PATH_COMMANDS = 'MLHVCSQTAZ'
MAX_NODES = 46
OUTLINES_CONTAINER_ID = 'SELECTION'
POLYGON_NODES = ['polygon', 'path', 'polyline', 'rect']
GARBAGE_ATTRIBUTES = ['fill', 'stroke', 'style', 'display']
SQ = u'²'
GROUP_DICT = {'n': 'number',
              'p': 'pl',
              'f': 'floor_number',
              's': 'section_number',
              'b': 'building_number',
              'q': 'quarter_number',
              'v': 'variant_number',
              'a': 'mezzanine_number'}

#This dictionary helps translate xml tags generated by 1S (http://www.1c.ru/)
#into valid xml
DICTIONARY_1S = {
    'output': u'Выгрузка',
    'building_type': u'd2p1:ТипНедвижимости',
    'title': ['xmlns:d2p1', 'xmlns:d3p1'],
    'building_object': u'd3p1:ОбъектСтроительства',
    'apartment': u'd4p1:ОбъектНедвижимости',
    'number': 'xmlns:d4p1',
    'data': u'РеквизитыОбъектаНедвижимости',
    'phase': u'Фаза',
    'building': u'Корпус',
    'section': u'Секция',
    'floor': u'Этаж',
    'room_count': u'КоличествоКомнат',
    'square': u'Площадь',
    'project_no': u'НомерПроектный',
    'total_cost': u'Стоимость',
    'cost_per_meter': u'Цена',
    'status': u'ДоступностьКпродаже',
}

ALPHA = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
         'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
         'Z']
CYR_INDEX = ['', u'А', u'Б', u'В', u'Г', u'Д', u'Е']


class CellValueError(Exception):
    def __init__(self, cell_value=None, row_number=None, sheet_name=None,
                 column_number=None):
        message = 'Cell value error: "%s" (Sheet "%s", Row %s, Column #%s)' % \
                  (cell_value, sheet_name, row_number, column_number)
        print(message)
        Exception.__init__(self, message)


class AttrValueError(Exception):
    def __init__(self, attr_value=None):
        message = 'Node attribute error: "%s"' % attr_value
        Exception.__init__(self, message)


class Path(object):
    """Object for path calulations"""
    def __init__(self):
        self.current_point = None
        self.coord_pairs = []

    def update(self, new_point, append=True):
        self.current_point = new_point
        if append:
            self.coord_pairs.append(self.current_point)

    def move_to(self, x=0.0, y=0.0):
        self.update((x, y))

    def line_to(self, x=None, y=None, relative=True):
        """Line to new point"""
        if relative:
            if not x:
                x = 0.0
            if not y:
                y = 0.0
            x, y = (self.current_point[0] + float(x),
                    self.current_point[1] + float(y))
        else:
            if not x:
                x = self.current_point[0]
            if not y:
                y = self.current_point[1]
        self.update((x, y))


def mass_replace(string, word_dict):
    """
    Take a text and replace words that match the key in a dictionary
    with the associated value, return the changed text
    """
    for key in word_dict:
        try:
            string = string.replace(word_dict[key], key)
        except TypeError:
            for variant in word_dict[key]:
                string = string.replace(variant, key)

    return string


class Timer(object):
    def __enter__(self):
        self.__start = time.time()

    def __exit__(self, type, value, traceback):
        # Error handling here
        self.__finish = time.time()

    def duration_in_seconds(self):
        return self.__finish - self.__start


def progressbar(iterator, prefix='', size=30, mute=False):
    """A nice progressbar for stdout with 'time to completion'"""
    count = len(iterator)
    timer = Timer()

    def _show(_i, sec):
        if not mute:
            x = int(size * _i / count)
            sys.stdout.write("\r%s[%s%s] %i/%i EST: %s"
                             % (prefix, "#" * x, "." * (size - x), _i, count,
                                str(datetime.timedelta(
                                    seconds=sec * (count - _i)))))
            sys.stdout.flush()

    _show(0, 0)
    for i, item in enumerate(iterator):
        with timer:
            yield item
        _show(i+1, timer.duration_in_seconds())
    sys.stdout.write("\r  \r\n")


def strip_string(string):
    """Strip whitespace and others from a string"""
    string = string.strip(' \t\n\r"')
    return ' '.join(string.split())


def strip_alpha(string, except_=None):
    """Strip all chars except numerical, '.' and ',' from a string"""
    except_str = ''
    if except_:
        except_str = ''.join(list(except_))
    alpha = re.compile(r'[^\d.,%s]+' % except_str)
    return alpha.sub('', string)


def parse_shorthand(shorthand_str):
    """
    Parse a string / file path for grouped numbers and ranges.
    Return: a dict formed according to GROUP_DICT
    """
    from itertools import chain
    result = {}
    clean_name = shorthand_str
    groups = re.split(GROUP_SEPARATOR, clean_name)
    for group in groups:
        group_values = list(group)
        #read and remove group identifier
        group_id = group_values.pop(0)
        group_val_str = ''.join(group_values)
        #process ranges
        ranges = re.split(GROUP_RANGE_SEPARATOR, group_val_str)
        group_lists = []
        for range_str in ranges:
            range_ = re.split(RANGE_SEPARATOR, range_str)
            try:
                if len(range_) is 1:
                    range_ = [int(range_[0])]
                if len(range_) is 2:
                     range_ = range(int(range_[0]), int(range_[1])+1)
            except ValueError:
                raise BaseException(
                    'Incorrect value in shorthand '
                    'record "{record}"'.format(record=shorthand_str))
            group_lists.append(range_)
        try:
            result[GROUP_DICT[group_id]] = list(chain.from_iterable(group_lists))
        except KeyError:
            raise BaseException(
                'Incorrect key in shorthand '
                'record "{record}"'.format(record=shorthand_str))
    return result


def close_path(points):
    """
    Append first coord pair to the end of pair row if its not there.
    Useful when you have unclosed paths in svg
    """
    pairs = re.split(' ', points)
    if pairs[0] != pairs[-1]:
        pairs.append(pairs[0])
    return ' '.join(pairs)


def process_value(value, enforce_type='int', default=None):
    """'Safely' read a value while enforcing particular type"""
    result = default
    try:
        if type(value) in (str, unicode):
            #prepare strings for numeric types
            if enforce_type in ('int', 'float'):
                value = strip_string(value)
                value = strip_alpha(value)
                value = value.replace(',', '.')
            if enforce_type is 'int':
                try:
                    value = float(value.strip('.'))
                except ValueError:
                    pass

        if value is not None:
            type_to_enforce = getattr(__builtin__, enforce_type)
            result = type_to_enforce(value)
    except ValueError:
        pass
    return result


def read_attr(node, attr_name, enforce_type='int', default=0):
    """Read xml node attribute with `_process_string`"""
    value = node.getAttribute(attr_name)
    if type(value) is not enforce_type:
        try:
            return process_value(value, enforce_type, default)
        except ValueError:
            value = default
    else:
        return value


def read_cell(sheet, row_num, col_num, enforce_type='int', default=None,
              raise_exception=False):
    """Read xls cell with `process_value`"""
    cell_value = sheet.cell_value(row_num, col_num)
    try:
        return process_value(cell_value, enforce_type, default)
    except ValueError:
        if raise_exception:
            raise CellValueError(cell_value, row_num, sheet.name, col_num)
        else:
            return default


def process_node(node):
    """Get coordinate string from one of the accepted svg tags"""
    points = None
    if node.nodeName in ('polygon', 'polyline'):
        points = strip_string(node.getAttribute('points'))
    if node.nodeName == 'rect':
        points = process_rectangle_node(node)
    if node.nodeName == 'path':
        points = process_path_node(node)
    if points:
        points = close_path(points)
    return points


def process_rectangle_node(node):
    """Get coordinate string from `rect` tag"""
    x = float(node.getAttribute('x'))
    y = float(node.getAttribute('y'))
    width = float(node.getAttribute('width'))
    height = float(node.getAttribute('height'))
    pairs = [','.join((str(x), str(y))),
             ','.join((str(x+width), str(y))),
             ','.join((str(x+width), str(y+height))),
             ','.join((str(x), str(y+height))),
             ','.join((str(x), str(y)))]
    return ' '.join(pairs)


def clean_nodes(nodes, allowed_nodes=POLYGON_NODES):
    """Add only allowed nodes to the result"""
    result = []
    for node in nodes:
        if node.nodeName in allowed_nodes:
            result.append(node)
    return result


def remove_child_nodes(parent, except_id=None):
    """Remove all child nodes from parent except for that with `except_id`"""
    skipped_node = None
    victims = parent.childNodes
    #TODO investigate this
    while len(victims) > 0:
        for node in victims:
            if except_id:
                try:
                    if node.getAttribute('id') == except_id:
                        skipped_node = node
                except AttributeError:
                    pass
            parent.removeChild(node)
    if skipped_node:
        parent.appendChild(skipped_node)


def process_path_node(node):
    """Get coordinate string from `path` node"""
    d = node.getAttribute('d')
    matrix = None
    try:
        transform = node.getAttribute('transform')
        matrix = parse_transform(transform)
    except AttributeError:
        pass
    paths = d.split('z')
    pattern = re.compile('([{com}]?[^{com}]+)'.format(com=SVG_PATH_COMMANDS),
                         re.IGNORECASE)
    #for now we're interested in first path only
    commands = pattern.findall(paths[0])
    path = Path()
    for command in commands:
        cmd_list = list(command)
        action = str(cmd_list.pop(0))
        command = ''.join(cmd_list)
        coord_pattern = re.compile('([\-]?[^\-,\s]+)')
        coords = coord_pattern.findall(command.strip())
        coords = [float(coord) for coord in coords]
        #TODO rewrite with dict
        if action == 'M':
            assert len(coords) is 2
            path.move_to(x=coords[0], y=coords[1])
        if action == 'h':
            assert len(coords) is 1
            path.line_to(x=coords[0])
        if action == 'H':
            assert len(coords) is 1
            path.line_to(x=coords[0], relative=False)
        if action == 'v':
            assert len(coords) is 1
            path.line_to(y=coords[0])
        if action == 'V':
            assert len(coords) is 1
            path.line_to(y=coords[0], relative=False)
        if action == 'l':
            assert len(coords) is 2
            path.line_to(x=coords[0], y=coords[1])
        if action == 'L':
            assert len(coords) is 2
            path.line_to(x=coords[0], y=coords[1], relative=False)
        if action == 'c':
            assert len(coords) is 6
            path.line_to(x=coords[4], y=coords[5])
        if action == 'C':
            assert len(coords) is 6
            path.line_to(x=coords[4], y=coords[5], relative=False)
    if matrix:
        new_pairs = []
        for pair in path.coord_pairs:
            new_pair = apply_matrix_to_point(pair, matrix)
            new_pairs.append(new_pair)
        path.coord_pairs = new_pairs
    return ' '.join('%s,%s' % pair for pair in path.coord_pairs)


def get_outline_container(dom_, skip_id_check=False):
    """
    Get outline container from svg.
    To qualify the container it must be *the first* `g` element and
    must have `id` attribute equal to OUTLINES_CONTAINER_ID
    """
    container = None
    first_g = dom_.getElementsByTagName('g')[0]
    if (not skip_id_check and first_g.getAttribute('id') ==
            OUTLINES_CONTAINER_ID) or skip_id_check:
        container = first_g
    return container


def remove_duplicates(points):
    """Return points without duplicates"""
    pairs = re.split(' ', points)
    clean_coords = []
    for pair in pairs:
        if pair not in clean_coords:
            clean_coords.append(pair)
    return ' '.join(clean_coords)


def get_polygon_nodes(file_path=None, dom_=None, skip_id_check=False,
                      skip_count_check=False):
    """Return polygon nodes found in svg file/string or dom"""
    if not dom_:
        try:
            dom_ = parse(file_path)
        except IOError:
            try:
                dom_ = parseString(file_path)
            except xml.parsers.expat.ExpatError:
                print('Bad xml: %s' % file_path)
    outline_nodes = None
    view_box = get_svg_viewbox(dom_)
    outline_container = get_outline_container(dom_, skip_id_check)
    if outline_container:
        outline_nodes = clean_nodes(outline_container.childNodes)
        for node in outline_nodes:
            node.margin_x = view_box['margin_x']
            node.margin_y = view_box['margin_y']
            node.width_ = view_box['width']
            node.height_ = view_box['height']
        nodes_count = len(outline_nodes)
        if not skip_count_check:
            assert nodes_count <= MAX_NODES, 'too many nodes(%d) in %s' % \
                                             (nodes_count, file_path)
    return outline_nodes


def get_text_nodes(file_path=None, dom_=None):
    """Return text nodes found in svg file/string or dom"""
    if not dom_:
        try:
            dom_ = parse(file_path)
        except IOError:
            dom_ = parseString(file_path)
    outlines_container = get_outline_container(dom_)
    nodes = clean_nodes(outlines_container.childNodes, ('text',))
    return nodes


def get_svg_viewbox(dom):
    """Get margin and size from svg node"""
    svg_node = dom.getElementsByTagName('svg')[0]
    view_box = svg_node.getAttribute('viewBox')
    sizes = view_box.split(' ')
    return {'margin_x': float(sizes[0]),
            'margin_y': float(sizes[1]),
            'width': float(sizes[2]),
            'height': float(sizes[3])}


def node2svg(node):
    """Insert a node into svg template"""
    template = pkg_resources.resource_string(__name__, 'svg.mako')
    svg_template = mako.template.Template(template)
    return svg_template.render(node=node)


def fix_pdf(file_path):
    """Write EOF marker into the end of pdf"""
    with open(file_path, 'a') as f:
        f.write('%%EOF%%')


def get_options(*args):
    """Provide some common input options for a script"""
    OPTIONS_DICT = {
        'all_available': {
            'short_key': '-a',
            'long_key': '--all-available',
            'default': False,
            'help_string': 'Make all flats available for sale',
            'metavar': 'ALL_AVAILABLE',
        },
        'limit': {
            'short_key': '-l',
            'long_key': '--limit',
            'default': None,
            'help_string': 'Limit number of output to',
            'metavar': 'LIMIT',
        },
        'shorthand': {
            'short_key': '-s',
            'long_key': '--shorthand',
            'default': dict(),
            'help_string': 'Ranges of values in shorthand',
            'metavar': 'SHORTHAND',
        },
        'dry_run': {
            'short_key': '-d',
            'long_key': '--dry_run',
            'default': None,
            'help_string': 'Perform a dry run',
            'metavar': 'DRY_RUN'
        },
        'subjects': {
            'short_key': '-w',
            'long_key': '--subjects',
            'default': None,
            'help_string': 'What to generate, separated with commas',
            'metavar': 'SKIP_MISSING'
        }
    }

    from optparse import OptionParser
    parser = OptionParser()

    for option in args:
        assert option in OPTIONS_DICT, 'Unknown option "%s"!' % option
        option_dict = OPTIONS_DICT[option]
        parser.add_option(option_dict['short_key'],
                          option_dict['long_key'], dest=option,
                          default=option_dict['default'],
                          help=option_dict['help_string'],
                          metavar=option_dict['metavar'])

    (options, args) = parser.parse_args()
    if hasattr(options, 'subject'):
        options.subjects = options.subjects.split(',')
    return options


def create_dirs_in_path(path):
    """Recursively create directories in path"""
    output_path = os.path.dirname(path)
    try:
        os.makedirs(output_path)
    except OSError as exception:
        import errno
        if exception.errno != errno.EEXIST:
            raise


def simple_multiprocess(callable_, args, processes=2):
    """Basic multiprocessing with Pool"""

    from multiprocessing import Pool

    pool = Pool(processes=processes)
    return pool.map(callable_, args)


def area_coords(node):
    """Return polygon coordinates in html area tag format removing margin"""
    result = None
    coords = process_node(node)
    try:
        points = re.split(' ', coords)
        pairs = []
        for pair in points:
            x, y = re.split(',', pair)
            margin_x = 0
            margin_y = 0
            try:
                margin_x = float(node.margin_x)
                margin_y = float(node.margin_y)
            except AttributeError:
                pass
            new_pair = ','.join([('%d' % (float(x)-margin_x)),
                                 ('%d' % (float(y)-margin_y))])
            pairs.append(new_pair)
        result = ','.join(pairs)
    except TypeError:
        pass
    return result


def set_style_value(node, style_name, style_value):
    """Set style record for a node, removing correspondant attribute"""

    style_value = str(style_value)
    styles_str = node.getAttribute('style')
    new_styles = []
    if styles_str:
        styles = styles_str.split(';')
        for style in styles:
            if style:
                name, value = style.split(':')
                if name == style_name:
                    style = ':'.join([name, style_value])
            new_styles.append(style)
            if node.hasAttribute(style_name):
                node.removeAttribute(style_name)
    else:
        new_styles.append(':'.join([style_name, style_value]))
    node.setAttribute('style', ';'.join(new_styles))


def make_opaque(path=None, data=None):
    """
    Sets opacity to 1 for `g` nodes in the svg file
    and return it as string
    """
    dom_ = None
    if path:
        dom_ = parse(path)
    if data:
        dom_ = parseString(data)
    g_nodes = dom_.getElementsByTagName('g')
    for node in g_nodes:
        set_style_value(node, 'opacity', 1)
    return dom_.toxml(encoding='utf-8')


def get_text_coords(node):
    """Get the text node from node and return its coordinates as tuple"""
    matrix_string = strip_alpha(node.getAttribute('transform'), except_=' ')
    #we get something like this "1 0 0 1 400.3262 253.96"
    matrix_values = matrix_string.split(' ')
    return float(matrix_values[4]), float(matrix_values[5])


def prepare_outline_svg(dom_):
    """
    Remove all `g` nodes other than OUTLINES_CONTAINER from an svg dom.
    Remove all separate styling attributes from outline nodes (like `fill`,
    `stroke` etc.) in exchange for single `style` attribute. This is made for
    compatibility with `pycairo`.
    """
    svg_node = dom_.getElementsByTagName('svg')[0]
    remove_child_nodes(svg_node, except_id=OUTLINES_CONTAINER_ID)
    outline_container = dom_.getElementsByTagName('g')[0]
    for outline_node in outline_container.childNodes:
        for attr_name in GARBAGE_ATTRIBUTES:
            try:
                if outline_node.hasAttribute(attr_name):
                    outline_node.removeAttribute(attr_name)
            except AttributeError:
                pass


def best_fit(context, box_dim=(0, 0), svg_dim=(0, 0), start_coords=(0, 0)):
    """
    Scale context to best fit into box
    Credit: http://stackoverflow.com/a/5514344/216042
    Gravity to center
    """
    box_width = box_dim[0]
    box_height = box_dim[1]
    svg_width = svg_dim[0]
    svg_height = svg_dim[1]
    x_start, y_start = start_coords

    # formulas
    width_ratio = float(box_width) / svg_width
    height_ratio = float(box_height) / svg_height
    scale = min(height_ratio, width_ratio)

    x_margin = (box_width - svg_width * scale) / 2
    y_margin = (box_height - svg_height * scale) / 2

    context.translate(x_start + x_margin, y_start + y_margin)
    context.scale(scale, scale)


def pad_box(canvas_size=(100, 100), padding=(0, 0, 0, 0)):
    """
    Pad the box (cut the size and pad coords) and return new dimensions
    and coords as tuple of tuples
    """
    canvas_width, canvas_height = canvas_size
    padding_top, padding_right, padding_bottom, padding_left = padding
    padded_box_dim = (canvas_width - padding_right - padding_left,
                      canvas_height - padding_top - padding_bottom)
    return padded_box_dim, (padding_left, padding_top)


def write_scaled_svg(dom_, size, filename, padding):
    """Write scaled svg file with pycairo and rsvg"""
    import rsvg, cairo
    #TODO try to rewrite without file generation
    surface = cairo.SVGSurface(filename, size[0], size[1])
    context = cairo.Context(surface)
    svg = rsvg.Handle(data=dom_.toxml('utf-8'))
    w, h, w1, h1 = svg.get_dimension_data()
    padded_box_dim, padded_box_coords = pad_box(size, padding)
    best_fit(context, padded_box_dim, (w, h), start_coords=padded_box_coords)
    svg.render_cairo(context)
    surface.finish()


def mass_set_style(attribs, svg_path=None, svg_data=None,
                   tag_names=('line', 'polygon', 'circle', 'polyline',
                              'rect', 'path')):
    """Set style defined in `attribs` dict for particular tags in svg."""
    if svg_path:
        dom_ = parse(svg_path)
    if svg_data:
        dom_ = parseString(svg_data)
    for tag_name in tag_names:
        nodes = dom_.getElementsByTagName(tag_name)
        for node in nodes:
            for name, value in attribs.items():
                set_style_value(node, name, value)
    return dom_.toxml(encoding='utf-8')


def mass_set_attr(attribs, svg_path=None, svg_data=None,
                  tag_names=('line', 'polygon', 'circle', 'polyline',
                             'rect', 'path')):
    """Set attribute defined in `attribs` dict for particular tags in svg."""
    if svg_path:
        dom_ = parse(svg_path)
    if svg_data:
        dom_ = parseString(svg_data)
    for tag_name in tag_names:
        nodes = dom_.getElementsByTagName(tag_name)
        for node in nodes:
            node.setAttribute('style', '')
            for name, value in attribs.items():
                node.setAttribute(name, str(value))
    return dom_.toxml(encoding='utf-8')


def set_svg_attribs(attribs, svg_path=None, svg_data=None):
    """Set attributes for an SVG element"""
    if svg_path:
        dom_ = parse(svg_path)
    if svg_data:
        dom_ = parseString(svg_data)
    svg_element = dom_.getElementsByTagName('svg')[0]
    for name, value in attribs.items():
        svg_element.setAttribute(name, str(value))
    return dom_.toxml(encoding='utf-8')


def remove_outlines(svg_path=None, svg_data=None):
    """Remove outlines container identified by OUTLINES_CONTAINER_ID"""
    dom_ = None
    if svg_path:
        dom_ = parse(svg_path)
    if svg_data:
        dom_ = parseString(svg_data)
    outlines_container = get_outline_container(dom_)
    doc_root = dom_.documentElement
    doc_root.removeChild(outlines_container)
    return dom_.toxml(encoding='utf-8')


def apply_matrix_to_point(point, matrix=(1, 0, 0, 1, 0, 0)):
    """Return a point (tuple) with applied transform matrix"""
    x, y = point
    a, b, c, d, e, f = matrix
    new_x = a * x + c * y + e
    new_y = b * x + d * y + f
#    print(x, new_x)
    return new_x, new_y


def parse_transform(transform_string):
    """
    Parse `transform` attribute into matrix tuple
    Credit: simpletransform.py
    """
    result=re.match("(translate|scale|rotate|skewX|skewY|matrix)\(([^)]*)\)",
                    transform_string)
    matrix = None

    #-- translate --
    if result.group(1) == "translate":
        args=result.group(2).split(",")
        dx=float(args[0])
        if len(args) == 1:
            dy = 0.0
        else:
            dy = float(args[1])
        matrix = (1, 0, dx, 0, 1, dy)

    #-- scale --
    if result.group(1) == "scale":
        args = result.group(2).split(",")
        sx = float(args[0])
        if len(args) == 1:
            sy = sx
        else:
            sy = float(args[1])
        matrix = (sx, 0, 0, 0, sy, 0)

    #-- rotate --
    if result.group(1) == "rotate":
        args = result.group(2).split(",")
        a = float(args[0]) * math.pi / 180
        if len(args) == 1:
            cx, cy = (0.0, 0.0)
        else:
            cx, cy = args[1:]
        matrix = (math.cos(a), -math.sin(a), cx, math.sin(a), math.cos(a), cy)

    #-- skewX --
    if result.group(1) == "skewX":
        a = float(result.group(2)) * math.pi / 180
        matrix = (1, math.tan(a), 0, 0, 1, 0)

    #-- skewX --
    if result.group(1) == "skewX":
        a = float(result.group(2)) * math.pi / 180
        matrix = (1, 0, 0, math.tan(a), 1, 0)

    #-- matrix --
    if result.group(1) == "matrix":
        a, b, c, d, e, f = result.group(2).split(",")
        matrix = (float(a), float(b), float(c), float(d), float(e), float(f))

    return matrix


def to_list(value):
    """
    Return value as a list. If the value is not iterable -
    return a list with a single element
    """
    list_ = list()
    #TODO a more pythonic way required
    if type(value) not in [str, int, unicode]:
        list_.extend(value)
    else:
        list_.append(value)
    return list_


def sort_nicely(list_):
    """
    Sort the given list in the way that humans expect.
    Credit: http://nedbatchelder.com/blog/200712/human_sorting.html
    (comment by Toothy)
    """
    convert = lambda text: int(text) if text.isdigit() else text
    alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
    list_.sort(key=alphanum_key)

def localize(value, monetary=False, locale_str='ru_RU.UTF-8', decoded=True,
             replace_utf_space=True, default=0):
    """Return localized value"""
    if not value:
        value = default
    locale.setlocale(locale.LC_ALL, locale_str)
    result = locale.format('%g', value, grouping=True, monetary=monetary)
    if decoded:
        result = result.decode('utf-8')
        if replace_utf_space:
            result = result.replace(u'\xa0', ' ')
    return result