from __future__ import absolute_import
from __future__ import unicode_literals

import datetime
import numbers
from functools import partial

import pytz
from django.urls import reverse
from django.utils.translation import ugettext as _

from casexml.apps.case.views import get_wrapped_case
from corehq.apps.hqwebapp.templatetags.proptable_tags import get_display_data
from corehq.form_processor.interfaces.dbaccessors import CaseAccessors


def case_hierarchy_context(case, get_case_url, show_view_buttons=True, timezone=None):
    wrapped_case = get_wrapped_case(case)
    if timezone is None:
        timezone = pytz.utc
    columns = wrapped_case.related_cases_columns
    type_info = wrapped_case.related_type_info

    case_list = get_flat_descendant_case_list(
        case, get_case_url, type_info=type_info
    )

    for c in case_list:
        if not c:
            continue
        c.columns = []
        case_dict = get_wrapped_case(c).to_full_dict()
        for column in columns:
            c.columns.append(get_display_data(
                case_dict, column, timezone=timezone))

    return {
        'current_case': case,
        'domain': case.domain,
        'case_list': case_list,
        'columns': columns,
        'num_columns': len(columns) + 1,
        'show_view_buttons': show_view_buttons,
    }


def normalize_date(val):
    # Can't use isinstance since datetime is a subclass of date.
    if type(val) == datetime.date:
        return datetime.datetime.combine(val, datetime.time.min)

    return val


def get_inverse(val):
    if isinstance(val, (datetime.datetime, datetime.date)):
        return datetime.datetime.max - val
    elif isinstance(val, numbers.Number):
        return 10 ** 20
    elif val is None or isinstance(val, bool):
        return not val
    else:
        raise Exception("%r has uninversable type: %s" % (val, type(val)))


def sortkey(child, type_info=None):
    """Return sortkey based on sort order defined in type_info, or use default
    based on open/closed and opened_on/closed_on dates.
    """
    type_info = type_info or {}
    case = child['case']
    if case.closed:
        key = [1]
        try:
            for attr, direction in type_info[case.type]['closed_sortkeys']:
                val = normalize_date(getattr(case, attr))
                if direction.lower() == 'desc':
                    val = get_inverse(val)
                key.append(val)
        except KeyError:
            key.append(datetime.datetime.max - case.closed_on)
    else:
        key = [0]
        try:
            for attr, direction in type_info[case.type]['open_sortkeys']:
                val = normalize_date(getattr(case, attr))
                if direction.lower() == 'desc':
                    val = get_inverse(val)
                key.append(val)
        except KeyError:
            key.append(case.opened_on or datetime.datetime.min)
    return key


def get_session_data(case, current_case, type_info):
    # this logic should ideally be implemented in subclasses of
    # CommCareCase
    if type_info and case.type in type_info:
        attr = type_info[case.type]['case_id_attr']
        return {
            attr: case.case_id,
            'case_id': current_case.case_id
        }
    else:
        return {
            'case_id': case.case_id
        }


TREETABLE_INDENT_PX = 19


def process_case_hierarchy(case_output, get_case_url, type_info):
    current_case = case_output['case']
    submit_url_root = reverse('receiver_post', args=[current_case.domain])
    form_url_root = reverse('formplayer_main', args=[current_case.domain])

    def process_output(case_output, depth=0):
        for c in case_output['child_cases']:
            process_output(c, depth=depth + 1)

        case = case_output['case']
        common_data = {
            'indent_px': depth * TREETABLE_INDENT_PX,
            'submit_url_root': submit_url_root,
            'form_url_root': form_url_root,
            'view_url': get_case_url(case.case_id),
            'session_data': get_session_data(case, current_case, type_info)
        }
        data = type_info.get(case.type, {})
        if 'description_property' in data:
            data['description'] = getattr(case, data['description_property'], None)
        if 'edit_session_data' in data:
            data['session_data'].update(data['edit_session_data'])
        data.update(common_data)

        case.edit_data = data

        if 'child_type' in data and not case.closed:
            child_type = data['child_type']
            child_data = type_info.get(child_type, {})
            child_data.update(common_data)
            child_data.update({
                "link_text": _("Add %(case_type)s") % {
                    'case_type': child_data.get('type_name', child_type)
                },
                "parent_node_id": case.case_id,
            })

            if 'create_session_data' in child_data:
                child_data['session_data'].update(child_data['create_session_data'])
            case.add_child_data = child_data

    process_output(case_output)


def get_children(case, type_info=None):
    def _get_children(case, referenced_type=None, seen=None):
        seen = seen or set()

        # only relevant for `pact`
        ignore_types = type_info.get(case.type, {}).get("ignore_relationship_types", []) if type_info else []
        if referenced_type and referenced_type in ignore_types:
            return None

        seen.add(case.case_id)
        children = [
            _get_children(i.referenced_case, i.referenced_type, seen) for i in case.reverse_indices
            if i.referenced_id not in seen
        ]

        children = [c for c in children if c is not None]

        # non-first-level descendants
        descendant_types = []
        for c in children:
            descendant_types.extend(c['descendant_types'])
        descendant_types = list(set(descendant_types))

        children = sorted(children, key=partial(sortkey, type_info=type_info))

        # set parent_case_id used by flat display
        for c in children:
            if not hasattr(c['case'], 'treetable_parent_node_id'):
                c['case'].treetable_parent_node_id = case.case_id

        child_cases = []
        for c in children:
            child_cases.extend(c['case_list'])

        return {
            'case': case,
            'child_cases': children,
            'descendant_types': list(set(descendant_types + [c['case'].type for c in children])),
            'case_list': [case] + child_cases
        }
    return _get_children(case)


def get_case_hierarchy(case, type_info):
    accessor = CaseAccessors(case.domain)
    new_indices = {case.case_id}
    seen_indices = {case.case_id}
    last_index = None
    while True:
        new_indices = accessor.get_indexed_case_ids(new_indices)
        if not set(new_indices) - seen_indices:
            break
        last_index = new_indices[0]
        seen_indices |= set(new_indices)

    return get_children(CaseAccessors(case.domain).get_case(last_index), type_info)


def get_flat_descendant_case_list(case, get_case_url, type_info=None):
    type_info = type_info or {}
    hierarchy = get_case_hierarchy(case, type_info)
    process_case_hierarchy(hierarchy, get_case_url, type_info)
    return hierarchy['case_list']

@@ -6753,20 +6753,28 @@
 index = 
-None
+case.case_id
 %0A    whi

Set last_index to be the highest ancestor