from __future__ import absolute_import
from flask import url_for, render_template, redirect, abort, flash, request,\
        Blueprint, current_app, jsonify
from flask.ext.login import login_required, current_user
from flask.ext.wtf import Form
import six
from six.moves import map
from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
from wtforms.fields import StringField, SubmitField, HiddenField, SelectField,\
        Label
from wtforms.validators import InputRequired, AnyOf, NumberRange

from ..models import db
from ..auth import PermissionType
from ..auth.models import Division, Permission, Entity


blueprint = Blueprint('divisions', __name__)


@blueprint.route('/')
@login_required
def list_divisions():
    """Show a page listing all divisions.

    Accesible only to administrators.
    """
    if current_user.admin:
        return render_template('divisions.html',
                divisions=Division.query.all())
    if current_user.has_permission(PermissionType.admin):
        admin_permissions = current_user.permissions.filter_by(
                permission=PermissionType.admin).values(Permission.division_id)
        admin_permissions = map(lambda x: x[0], admin_permissions)
        divisions = db.session.query(Division).\
                filter(Division.id.in_(admin_permissions))
        return render_template('divisions.html', divisions=divisions)
    return abort(403)


class AddDivisionForm(Form):
    name = StringField(u'Division Name', validators=[InputRequired()])
    submit = SubmitField(u'Create Division')


@blueprint.route('/add/', methods=['GET', 'POST'])
@login_required
def add_division():
    """Present a form for adding a division and also process that form.

    Only accesible to adminstrators.
    """
    if not current_user.admin:
        return abort(403)
    form = AddDivisionForm()
    if form.validate_on_submit():
        division = Division(form.name.data)
        db.session.add(division)
        db.session.commit()
        return redirect(url_for('.division_detail', division_id=division.id))
    return render_template('form.html', form=form)


class ChangeEntity(Form):
    form_id = HiddenField(default='entity')
    id_ = HiddenField()
    name = StringField()
    permission = HiddenField(validators=[AnyOf(list(PermissionType.values()))])
    action = HiddenField(validators=[AnyOf(('add', 'delete'))])


#: List of tuples enumerating attributes that can be transformed/linked.
#: Mainly used as the choices argument to :py:class:`~.SelectField`
transformer_choices = [
    ('', u''),
    ('kill_timestamp', u'Kill Timestamp'),
    ('pilot', u'Pilot'),
    ('corporation', u'Corporation'),
    ('alliance', u'Alliance'),
    ('system', u'Solar System'),
    ('constellation', u'Constellation'),
    ('region', u'Region'),
    ('ship_type', u'Ship'),
    ('payout', u'Payout'),
    ('status', u'Request Status'),
]


class ChangeTransformer(Form):
    form_id = HiddenField(default='transformer')
    attribute = SelectField(u'Attribute', choices=transformer_choices)
    transformer = SelectField(u'Transformer', choices=[])


def transformer_choices(attr):
    """Conveniece function for generating a list of transformer option tuples.

    :param attr str: the name of the attribute to make a list for.
    :return: A list of tuples suitable for the choices argument of\
        :py:class:`StringField`
    :rtype: list
    """
    default_transformers = [
        ('none', 'None'),
    ]
    choices = default_transformers
    if attr in current_app.url_transformers:
        for transformer in current_app.url_transformers[attr]:
            choices.append((transformer, transformer))
    return choices


@blueprint.route('/<int:division_id>/', methods=['GET', 'POST'])
@login_required
def division_detail(division_id):
    """Generate a page showing the details of a division.

    Shows which groups and individuals have been granted permissions to each
    division.

    Only accesible to administrators.

    :param int division_id: The ID number of the division
    """
    division = Division.query.get_or_404(division_id)
    if not current_user.admin and not \
            current_user.has_permission(PermissionType.admin, division):
        abort(403)
    if request.method == 'POST':
        if request.form['form_id'] == 'entity':
            form = ChangeEntity()
            if form.validate():
                if form.id_.data != '':
                    entity = Entity.query.get(form.id_.data)
                    if entity is None:
                        flash(u"No entity with ID #{}.".format(form.id_.data),
                                u'error')
                else:
                    try:
                        entity = Entity.query.filter_by(
                                name=form.name.data).one()
                    except NoResultFound:
                        flash(u"No entities with the name '{}' found.".
                                format(form.name.data), category=u'error')
                    except MultipleResultsFound:
                        flash(u"Multiple entities eith the name '{}' found.".
                                format(form.name.data), category=u'error')
                permission_type = PermissionType.from_string(
                        form.permission.data)
                permission_query = Permission.query.filter_by(
                        division=division,
                        entity=entity,
                        permission=permission_type)
                try:
                    permission = permission_query.one()
                except NoResultFound:
                    if form.action.data == 'add':
                        db.session.add(
                            Permission(division, permission_type, entity))
                        flash(u"'{}' is now a {}.".format(entity,
                                permission_type.description.lower()), u"info")
                    elif form.action.data == 'delete':
                        flash(u"{} is not a {}.".format(entity,
                            permission_type.description.lower()), u"warning")
                else:
                    if form.action.data == 'delete':
                        permission_query.delete()
                        flash(u"'{}' is no longer a {}.".format(entity,
                                permission_type.description.lower()), u"info")
                    elif form.action.data == 'add':
                        flash(u"'{}' is now a {}.".format(entity,
                                permission_type.description.lower()), u"info")
            else:
                abort(400)
        elif request.form['form_id'] == 'transformer':
            form = ChangeTransformer()
            attr = form.attribute.data
            form.transformer.choices = transformer_choices(attr)
            # Check form and go from there
            if form.validate():
                name = form.transformer.data
                if name == 'none':
                    division.transformers[attr] = None
                else:
                    # Get the specific map of transformers for the attribute
                    attr_transformers = current_app.url_transformers[attr]
                    # Get the new transformer
                    division.transformers[attr] = attr_transformers[name]
                    # Explicitly add the TransformerRef to the session
                    db.session.add(division.division_transformers[attr])
            else:
                abort(400)
        db.session.commit()
    return render_template(
            'division_detail.html',
            division=division,
            entity_form=ChangeEntity(),
            transformer_form=ChangeTransformer(),
    )


@blueprint.route('/<int:division_id>/transformers/')
@blueprint.route('/<int:division_id>/transformers/<attribute>/')
@login_required
def list_transformers(division_id, attribute=None):
    """API method to get a list of transformers for a division.

    :param division_id int: the ID of the division to look up
    :param attribute str: a specific attribute to look up. Optional.
    :return: JSON
    """
    division = Division.query.get_or_404(division_id)
    if not current_user.admin and not \
            current_user.has_permission(PermissionType.admin, division):
        abort(403)
    if attribute is None:
        attrs = six.iterkeys(current_app.url_transformers)
    else:
        attrs = (attribute,)
    choices = {}
    for attr in attrs:
        raw_choices = transformer_choices(attr)
        current = division.transformers.get(attr, None)
        if current is not None:
            choices[attr] = \
                    [(c[0], c[1], c[1] == current.name) for c in raw_choices]
        else:
            choices[attr] = \
                    [(c[0], c[1], False) for c in raw_choices]
    return jsonify(choices)
