import re
from flask import Blueprint, render_template, flash, redirect, url_for, request, send_file
from flask.ext.login import login_required
from flask.ext.babel import gettext, ngettext, lazy_gettext
from forms import GeneralModelConverter
from .filemanager import uuid_originalname
from .security.decorators import has_access
from .widgets import FormWidget, ShowWidget, ListWidget, SearchWidget
from .actions import ActionItem


def expose(url='/', methods=('GET',)):
    """
        Use this decorator to expose views in your view classes.
       
        :param url:
            Relative URL for the view
        :param methods:
            Allowed HTTP methods. By default only GET is allowed.
    """
    def wrap(f):
        if not hasattr(f, '_urls'):
            f._urls = []
        f._urls.append((url, methods))
        return f
    return wrap
    
    
class BaseView(object):
    """
    Base View for all Views
    includes permission, app info
    """

    baseapp = None
    blueprint = None
    endpoint = None
    
    route_base = None
    template_folder = 'templates'
    static_folder='static'
    
    base_permissions = []
    actions = []
    
    default_view = 'list'

    def __init__(self):
        """
        Initialization of base permissions
        based on exposed methods and actions
        """
        if not self.base_permissions:
            self.base_permissions = [
                ('can_' + attr_name)
                for attr_name in dir(self)
                if hasattr(getattr(self, attr_name),'_urls')
                ]
            for attr_name in dir(self):
                if hasattr(getattr(self, attr_name),'_action'):
                    action = ActionItem(*attr_name._action, func = getattr(self, attr_name))
                    self.base_permissions.append(action.name)
                    self.actions.append(action)



    def create_blueprint(self, baseapp, 
                        endpoint = None, 
                        static_folder = None):
        """
            Create Flask blueprint.
            
            param baseapp:
               the BaseApp application
            param endpoint:
               endpoint override for this blueprint, will assume class name if not provided
            param static_folder:
               the relative override for static forder, if omited application will use the baseapp static
        """
        # Store BaseApp instance
        self.baseapp = baseapp

        # If endpoint name is not provided, get it from the class name
        if not self.endpoint:
            self.endpoint = self.__class__.__name__

        if self.route_base is None:
            self.route_base = '/' + self.__class__.__name__.lower()

        self.static_folder = static_folder
        if not static_folder:
        # Create blueprint and register rules
            self.blueprint = Blueprint(self.endpoint, __name__,
                                   url_prefix=self.route_base,
                                   template_folder=self.template_folder)
        else:
            self.blueprint = Blueprint(self.endpoint, __name__,
                                   url_prefix=self.route_base,
                                   template_folder=self.template_folder,
                                   static_folder = static_folder)

        self._register_urls()
        return self.blueprint

    def _register_urls(self):
        for attr_name in dir(self):
            attr = getattr(self, attr_name)

            if hasattr(attr, '_urls'):
                for url, methods in attr._urls:
                    self.blueprint.add_url_rule(url,
                                        attr_name,
                                        attr,
                                        methods=methods)
        

    def _prettify_name(self, name):
        """
        Prettify pythonic variable name.

        For example, 'hello_world' will be converted to 'Hello World'

        :param name:
            Name to prettify
        """
        return re.sub(r'(?<=.)([A-Z])', r' \1', name)

    def _prettify_column(self, name):
        """
        Prettify pythonic variable name.

        For example, 'hello_world' will be converted to 'Hello World'

        :param name:
            Name to prettify
        """
        return name.replace('_', ' ').title()


    def _get_redirect(self):
        if (request.args.get('next')):
            return request.args.get('next')
        else:
            try:
                return url_for('%s.%s' % (self.endpoint, self.default_view))
            except:
                return '/'
            

    def _get_group_by_args(self):
        group_by = request.args.get('group_by')
        if not group_by: group_by = ''
        return group_by

    def _get_page_args(self, pages = {}):
        """
        Get page arguments, return a dictionary
        { <VIEW_NAME>: PAGE_NUMBER }
        
        Arguments are passed: page_<VIEW_NAME>=<PAGE_NUMBER>
        
        """
        for arg in request.args:
            re_match = re.findall('page_(.*)', arg)
            if re_match:
                pages[re_match[0]] = int(request.args.get(arg))
        return pages



    def _get_order_args(self, orders = {}):
        """
        Get order arguments, return a dictionary
        { <VIEW_NAME>: (ORDER_COL, ORDER_DIRECTION) }
        
        Arguments are passed like: _oc_<VIEW_NAME>=<COL_NAME>&_od_<VIEW_NAME>='asc'|'desc'
        
        """
        for arg in request.args:
            re_match = re.findall('_oc_(.*)', arg)
            if re_match:
                orders[re_match[0]] = (request.args.get(arg),request.args.get('_od_' + re_match[0]))
        return orders
                

    def _get_filter_args(self, filters={}):
        for arg in request.args:
            re_match = re.findall('_flt_(.*)', arg)
            if re_match:
                # ignore select2 __None value
                if request.args.get(arg) not in ('__None',''):
                    filters[re_match[0]] = request.args.get(arg)
        return filters


    def _get_dict_from_form(self, form, filters={}):
        for item in form:
            if item.data:
                filters[item.name] = item.data
        return filters



class IndexView(BaseView):
    """
    Index View
    A simple view that implements the index for the site
    """

    route_base = ''

    index_template = 'appbuilder/index.html'

    @expose('/')
    @expose('/index/')
    def index(self):
        return render_template(self.index_template, baseapp = self.baseapp)

"""
----------------------------------
     SIMPLE FORM VIEW
----------------------------------
"""
class SimpleFormView(BaseView):
    """
    Simple Form View
    Basic functions used on forms:
    override form_get and form_post
    """

    form_template = 'appbuilder/general/model/edit.html'
    """ Widgets """
    edit_widget = FormWidget

    form_title = 'Form Title'
    form_columns = []
    form = None
    
    @expose("/form", methods=['GET'])
    @has_access
    def this_form_get(self):
        form = self.form.refresh()
        self.form_get(form)
        widgets = self._get_edit_widget(form = form)
        return render_template(self.form_template,
            title = self.form_title,
            widgets = widgets,
            baseapp = self.baseapp
            )

    def form_get(self, form):
        """
        Override this method to implement your form processing
        """
        pass

    @expose("/form", methods=['POST'])
    @has_access
    def this_form_post(self):
        form = self.form.refresh()
        if form.validate_on_submit():
            self.form_post(form)
            return redirect(self._get_redirect())
        else:
            widgets = self._get_edit_widget(form = form)
            return render_template(
                    self.form_template,
                    title = self.form_title,
                    widgets = widgets,
                    baseapp = self.baseapp
                    )

    def form_post(self, form):
        """
        Override this method to implement your form processing
        """
        pass

    def _get_edit_widget(self, form = None, exclude_cols = [], widgets = {}):
        widgets['edit'] = self.edit_widget(route_base = self.route_base,
                                                form = form,
                                                exclude_cols = exclude_cols,
                                                )
        return widgets



class BaseCRUDView(BaseView):
    
    datamodel = None
    related_views = []
    
    """ Titles """
    list_title = ""
    show_title = ""
    add_title = ""
    edit_title = ""

    """ Include Columns """
    list_columns = []
    show_columns = []
    add_columns = []
    edit_columns = []
    order_columns = []
    search_columns = []

    label_columns = {}
    description_columns = {}
    
    page_size = 30

    """ fieldsets [(<'TITLE'|None>, {'fields':[<F1>,<F2>,...]}),....] """
    show_fieldsets = []
    add_fieldsets = []
    edit_fieldsets = []

    """ Forms """
    add_form = None
    edit_form = None
    search_form = None

    validators_columns = {}

    """ Template Layouts """
    list_template = 'appbuilder/general/model/list.html'
    edit_template = 'appbuilder/general/model/edit.html'
    add_template = 'appbuilder/general/model/add.html'
    show_template = 'appbuilder/general/model/show.html'

    """ Widgets """
    list_widget = ListWidget
    edit_widget = FormWidget
    add_widget = FormWidget
    show_widget = ShowWidget
    search_widget = SearchWidget

    
    """ Additional Widgets """
    show_additional_links = []

    
    def _init_forms(self):
        
        conv = GeneralModelConverter(self.datamodel)        
        if not self.add_form:
            self.add_form = conv.create_form(self.label_columns,
                    self.description_columns,
                    self.validators_columns,
                    self.add_columns)
        if not self.edit_form:
            self.edit_form = conv.create_form(self.label_columns,
                    self.description_columns,
                    self.validators_columns,
                    self.edit_columns)        
        if not self.search_form:
            self.search_form = conv.create_form(self.label_columns,
                    self.description_columns,
                    self.validators_columns,
                    self.search_columns)

    def _init_titles(self):
        if not self.list_title:
            self.list_title = 'List ' + self._prettify_name(self.datamodel.obj.__name__)
        if not self.add_title:
            self.add_title = 'Add ' + self._prettify_name(self.datamodel.obj.__name__)
        if not self.edit_title:
            self.edit_title = 'Edit ' + self._prettify_name(self.datamodel.obj.__name__)
        if not self.show_title:
            self.show_title = 'Show ' + self._prettify_name(self.datamodel.obj.__name__)

    def _init_vars(self):
        self._init_titles()
            
        list_cols = self.datamodel.get_columns_list()
        for col in list_cols:
            if not self.label_columns.get(col):
                self.label_columns[col] = self._prettify_column(col)
        if self.show_fieldsets:
            self.show_columns = []
            for fieldset_item in self.show_fieldsets:                
                self.show_columns = self.show_columns + list(fieldset_item[1].get('fields'))
        else:
            if not self.show_columns:
                self.show_columns = list_cols
        if self.add_fieldsets:
            self.add_columns = []
            for fieldset_item in self.add_fieldsets:
                self.add_columns = self.add_columns + list(fieldset_item[1].get('fields'))
        else:
            if not self.add_columns:
                self.add_columns = list_cols
        if self.edit_fieldsets:
            self.edit_columns = []
            for fieldset_item in self.edit_fieldsets:
                self.edit_columns = self.edit_columns + list(fieldset_item[1].get('fields'))
        else:
            if not self.edit_columns:
                self.edit_columns = list_cols
                        
        
    def __init__(self, **kwargs):
        self._init_vars()
        self._init_forms()
        super(BaseCRUDView, self).__init__(**kwargs)
    

    """
    --------------------------
            WIDGET METHODS
    --------------------------
    """
    def _get_related_list_widget(self, item, related_view, 
                                filters={}, order_column='', order_direction='',
                                page=None, page_size=None):

        fk = related_view.datamodel.get_related_fk(self.datamodel.obj)
        filters[fk] = item
        return related_view._get_list_widget(filters = filters, 
                    order_column = order_column, order_direction = order_direction, page=page, page_size=page_size)
        
    def _get_related_list_widgets(self, item, filters = {}, orders = {}, 
                                pages=None, widgets = {}, **args):
        widgets['related_lists'] = []
        for view in self.related_views:
            if orders.get(view.__class__.__name__):
                order_column, order_direction = orders.get(view.__class__.__name__)
            else: order_column, order_direction = '',''
            widgets['related_lists'].append(self._get_related_list_widget(item, view, filters, 
                    order_column, order_direction, page=pages.get(view.__class__.__name__), page_size=view.page_size).get('list'))
        return widgets
    
    def _get_list_widget(self, filters = {}, 
                        order_column = '', 
                        order_direction = '',
                        page = None,
                        page_size = None,
                        widgets = {}, **args):

        count, lst = self.datamodel.query(filters, order_column, order_direction, page=page, page_size=page_size)
        pks = self.datamodel.get_keys(lst)
        widgets['list'] = self.list_widget(route_base = self.route_base,
                                                label_columns = self.label_columns,
                                                include_columns = self.list_columns,
                                                value_columns = self.datamodel.get_values(lst, self.list_columns),
                                                order_columns = self.order_columns,
                                                page = page,
                                                page_size = page_size,
                                                count = count,
                                                pks = pks,
                                                filters = filters,
                                                generalview_name = self.__class__.__name__
                                                )
        return widgets

    def _get_search_widget(self, form = None, exclude_cols = [], widgets = {}):
        widgets['search'] = self.search_widget(route_base = self.route_base,
                                                form = form,
                                                exclude_cols = exclude_cols,
                                                )
        return widgets


    def _get_show_widget(self, id, widgets = {}, show_additional_links = []):
        if show_additional_links:
            additional_links = show_additional_links
        else: additional_links = self.show_additional_links
        item = self.datamodel.get(id)
        widgets['show'] = self.show_widget(route_base = self.route_base,
                                                pk = id,
                                                label_columns = self.label_columns,
                                                include_columns = self.show_columns,
                                                value_columns = self.datamodel.get_values_item(item, self.show_columns),
                                                additional_links = additional_links,
                                                fieldsets = self.show_fieldsets
                                                )
        return widgets


    def _get_add_widget(self, form = None, exclude_cols = [], widgets = {}):
        widgets['add'] = self.edit_widget(route_base = self.route_base,
                                                form = form,
                                                exclude_cols = exclude_cols,
                                                fieldsets = self.add_fieldsets
                                                )
        return widgets

    def _get_edit_widget(self, form = None, exclude_cols = [], widgets = {}):
        widgets['edit'] = self.edit_widget(route_base = self.route_base,
                                                form = form,
                                                exclude_cols = exclude_cols,
                                                fieldsets = self.edit_fieldsets
                                                )
        return widgets



    def debug(self):

        print self.__class__.__name__, "SHOW FS", self.show_fieldsets
        print self.__class__.__name__, "SHOW COL", self.show_columns
        print self.__class__.__name__, "ADD FS", self.add_fieldsets
        print self.__class__.__name__, "ADD COL", self.add_columns
        print self.__class__.__name__, "EDIT FS", self.edit_fieldsets
        print self.__class__.__name__, "EDIT COL", self.edit_columns
        print self.__class__.__name__, "LIST COL", self.list_columns

    """
    -----------------------------------
     Methods to override
    -----------------------------------
    """
    @classmethod
    def pre_update(self, item):
        pass

    @classmethod
    def post_update(self, item):
        pass

    @classmethod
    def pre_add(self, item):
        pass

    @classmethod
    def post_add(self, item):
        pass

    @classmethod
    def pre_delete(self, item):
        pass

    @classmethod
    def post_delete(self, item):
        pass



class GeneralView(BaseCRUDView):
    """
    General View Class
    Responsible to handle all CRUD events
    List, Edit, Add, Show based on models
    """

    def __init__(self, **kwargs):
        super(GeneralView, self).__init__(**kwargs)

    """
    --------------------------------
            LIST
    --------------------------------
    """
    
    @expose('/list/')
    @has_access
    def list(self):

        form = self.search_form.refresh()
        
        if self._get_order_args().get(self.__class__.__name__):
            order_column, order_direction = self._get_order_args().get(self.__class__.__name__)
        else: order_column, order_direction = '',''
        page = self._get_page_args().get(self.__class__.__name__)

        filters = {}
        filters = self._get_filter_args(filters)
        if (filters != {}):
            item = self.datamodel.obj()
            for filter_key in filters:
                # on related models translate id to model obj
                try:
                    rel_obj = self.datamodel.get_related_obj(filter_key, filters.get(filter_key))
                    setattr(item, filter_key, rel_obj)
                    form = self.search_form(obj = item)
                    filters[filter_key] = rel_obj
                except:
                    setattr(item, filter_key, filters.get(filter_key))
                    form = self.search_form(obj = item)

        widgets = self._get_list_widget(filters = filters, 
                    order_column = order_column, 
                    order_direction = order_direction, 
                    page = page, 
                    page_size = self.page_size)
        widgets = self._get_search_widget(form = form, widgets = widgets)

        return render_template(self.list_template,
                                        title = self.list_title,
                                        widgets = widgets,
                                        baseapp = self.baseapp)



    """
    --------------------------------
            SHOW
    --------------------------------
    """
    @expose('/show/<int:pk>', methods=['GET'])
    @has_access
    def show(self, pk):

        widgets = self._get_show_widget(pk)
        item = self.datamodel.get(pk)
        pages = self._get_page_args()
        orders = self._get_order_args()
        
        widgets = self._get_related_list_widgets(item, filters = {}, orders = orders, 
                pages = pages, widgets = widgets)
        return render_template(self.show_template,
                           pk = pk,
                           title = self.show_title,
                           widgets = widgets,
                           baseapp = self.baseapp,
                           related_views = self.related_views)



    """
    ---------------------------
            ADD
    ---------------------------
    """
    @expose('/add', methods=['GET', 'POST'])
    @has_access
    def add(self):

        filters = self._get_filter_args(filters={})

        form = self.add_form.refresh()
        exclude_cols = self.datamodel.get_relation_filters(filters)

        if form.validate_on_submit():
            item = self.datamodel.obj()
            form.populate_obj(item)
            for filter_key in exclude_cols:
                rel_obj = self.datamodel.get_related_obj(filter_key, filters.get(filter_key))
                setattr(item, filter_key, rel_obj)
                        
            self.pre_add(item)
            self.datamodel.add(item)
            self.post_add(item)
            return redirect(self._get_redirect())
        else:
            widgets = self._get_add_widget(form = form, exclude_cols = exclude_cols)
            return render_template(
                                                    self.add_template,
                                                    title = self.add_title,
                                                    widgets = widgets,
                                                    baseapp = self.baseapp
                                                    )    

    """
    ---------------------------
            EDIT
    ---------------------------
    """
    @expose('/edit/<int:pk>', methods=['GET', 'POST'])
    @has_access
    def edit(self, pk = 0):

        pages = self._get_page_args()
        orders = self._get_order_args()
        
        item = self.datamodel.get(pk)
        filters = self._get_filter_args(filters={})
        exclude_cols = self.datamodel.get_relation_filters(filters)

        if request.method == 'POST':
            form = self.edit_form(request.form)
            form = form.refresh(obj=item)
            if form.validate():
                form.populate_obj(item)

                for filter_key in exclude_cols:
                    rel_obj = self.datamodel.get_related_obj(filter_key, filters.get(filter_key))
                    setattr(item, filter_key, rel_obj)
                
                self.pre_update(item)
                self.datamodel.edit(item)
                self.post_update(item)
                return redirect(self._get_redirect())
            else:
                widgets = self._get_edit_widget(form = form, exclude_cols = exclude_cols)
                widgets = self._get_related_list_widgets(item, filters = {}, 
                        orders = orders, pages = pages, widgets = widgets)
                return render_template(self.edit_template,
                        title = self.edit_title,
                        widgets = widgets,
                        baseapp = self.baseapp,
                        related_views = self.related_views)
        else:
            form = self.edit_form(obj=item)
            form = form.refresh(obj=item)
            widgets = self._get_edit_widget(form = form, exclude_cols = exclude_cols)
            widgets = self._get_related_list_widgets(item, filters = {}, 
                        orders = orders, pages = pages, widgets = widgets)                
            return render_template(self.edit_template,
                            title = self.edit_title,
                            widgets = widgets,
                            baseapp = self.baseapp,
                            related_views = self.related_views)


    """
    ---------------------------
            DELETE
    ---------------------------
    """
    @expose('/delete/<int:pk>')
    @has_access
    def delete(self, pk):
        item = self.datamodel.get(pk)
        
        self.pre_delete(item)
        self.datamodel.delete(item)
        self.post_delete(item)        
        return redirect(self._get_redirect())


    @expose('/download/<string:filename>')
    @has_access
    def download(self, filename):
        return send_file(self.baseapp.app.config['UPLOAD_FOLDER'] + filename, 
                    attachment_filename = uuid_originalname(filename), 
                    as_attachment = True)
        



class AdditionalLinkItem():
    name = ""
    label = ""
    href = ""
    icon = ""

    def __init__(self, name, label, href, icon=""):
        self.name = name
        self.label = label
        self.href = href
        self.icon = icon
