#!/usr/bin/env python
# -*- coding: utf-8 -*-
import re

from django.test import TestCase

from lostplaces.models import Taggable, Mapable

from taggit.models import Tag

class ViewTestCase(TestCase):
    '''
    This is a Mixin for testing views. It provides functionality to
    test the context, forms and HTTP Response of responses. 
    All methods take responses, so this base class can be used
    with django's RequestFactory and Test-Client
    '''
    view = None
    
    def assertMessage(self, response, message_text, message_type, msg=None):
        self.assertNotEqual(
            None,
            re.search(
                """<div.*message.*%s.*>.*%s.*</div>""" % (
                    message_type.lower(),
                    message_text.lower()
                ),
                response.content.decode().replace('\n', '').lower()
            ),
            msg
        )
        
    
    def assertContext(self, response, key, value=None):
        '''
        Checks weather the response's context has the given key
        and, if passed, checks the value
        '''
        self.assertTrue(
            key in response.context,
            msg='Expecting the context of %s to have an attribute \'%s\'' % (
                self.view.__name__,
                key
            )
        )

        if value:
            self.assertEqual(
                value,
                response.context[key],
                msg='Expecting the context of %s to have %s set to \'%s\'' % (
                    self.view.__name__,
                    key, 
                    str(value)
                )
            )
        
    def assertHasForm(self, response, key, form_class):
        '''
        Checks if response has a form under the given key and if 
        the forms class matches.
        '''
        self.assertContext(response, key)
        self.assertEqual(
            type(response.context[key]),
            form_class,
            msg='Expecting %s\'s context.%s to be of the type %s' % (
                self.view.__name__,
                key,
                form_class.__name__
            ) 
        )

    def assertHttpCode(self, response, code):
        '''
        Checks if the response has the given status code
        '''
        self.assertEqual(
            response.status_code, code,
            msg='Expecting an HTTP %s response, but got HTTP %s' % (
                code,
                response.status_code
            )
        )
        
    def assertHttpRedirect(self, response, redirect_to=None):
        '''
        Checks weather the response redirected, and if passed, 
        if it redirected to the expected location
        '''
        
        self.assertTrue(
            300 <= response.status_code < 400,
            'Expected an HTTP 3XX (redirect) response, but got HTTP %s' %
            response.status_code
        )
        self.assertTrue(
            'location' in response,
            msg='Expecting a redirect to have an location, got none'
        )
        if redirect_to:
            self.assertEqual(
                response['location'],
                redirect_to,
                msg='Expecting the response to redirect to %s, where redirected to %s instead' % (
                    str(redirect_to),
                    str(response['location'])
                )
            )

    def assertHttpOK(self, response):
        self.assertHttpCode(response, 200)

    def assertHttpCreated(self, response):
        self.assertHttpCode(response, 201)

    def assertHttpBadRequest(self, response):
        self.assertHttpCode(response, 400)

    def assertHttpUnauthorized(self, response):
        self.assertHttpCode(response, 401)

    def assertHttpForbidden(self, response):
        self.assertHttpCode(response, 403)

    def assertHttpNotFound(self, response):
        self.assertHttpCode(response, 404)

    def assertHttpMethodNotAllowed(self, response):
        self.assertHttpCode(response, 405)

class GlobalTemplateTestCaseMixin:
    
    def assertGlobal(self, response):
        self.assertLogo(response)
        self.assertMenu(response)
        self.assertProfileMenu(response)

    def assertLogo(self, response):
        """
        Looks for an image tag with with 'logo' or 'Logo' in the file name
        all within an <header> tag.
        """
        self.assertNotEqual(
            None,
            re.search(
                """<header.*>.*<img.*src=("|').*(logo|Logo).*("|').*>.*</header>""",
                response.content.decode().replace('\n', '')
            ),
            msg='Expecting a header containing a logo with \'logo\' in the source name'
        )
        
    def assertMenu(self, response):
        """
        Looks for an '<nav>' tag containing a link to / and a link labeled 'urbex code.*'
        """
        self.assertNotEqual(
            None,
            re.search(
                """<nav.*>.*<a.*href=("|')/("|').*>.*<a.*>.*urbex code.*</nav>""",
                response.content.decode().replace('\n', '').lower()
            ),
            msg='Expecting an menu containing a link to the homepage and a link to the urbex codex'
        )
        
    def assertMenuItem(self, response, label=None, url=None):
        """
        Checks for an additional menu item by label or url. Regex can be passed.
        """
        if label is None and url is None:
            raise ValueError("Either a label or url has to be provided")
        
        found_label = None
        if label != None:
            label = label.lower()
            found_label = re.search(
                """<nav.*>.*<a.*>.*%s.*</a>.*</nav>""" % label,
                response.content.decode().replace('\n', '').lower()
            )
        
        found_url = None
        if url != None:
            url = url.lower()
            found_url = re.search(
                """<nav.*>.*<a.*href=("|')%s("|')>.*</a>.*</nav>""" % url,
                response.content.decode().replace('\n', '')
            )
        
        self.assertTrue(
            found_label != None and found_url != None,
            msg='Expecting an menu item with either label \'%s\' or url \'%s\'' % (
                label,
                url
            )
        )
        
    def assertProfileMenu(self, response):
        """
        Looks for a logout or login+signup link within the an header tag
        """
        self.assertNotEqual(
            None,
            re.search(
                """<header.*>.*(<a.*href=("|').*logout.*("|')|<a.*href=("|').*login.*("|').*<a.*href=("|').*signup.*("|')).*</header>""",
                response.content.decode().replace('\n', '').lower()
            ),
            msg='Expecting a profile menu containing either a logout link or a login and then a signup link'
        )
        
class TaggableViewTestCaseMixin:

    def assertTaggableContext(self, context):
        self.assertTrue(
            'all_tags' in context,
            msg='Expecting the context for taggable to contain an \'all_tags\' attribute'
        )
        
        for tag in context['all_tags']:
            self.assertTrue(
                isinstance(tag, Tag),
                msg='Expecting all entries to be an instance of %s, got %s' % (
                    str(Tag),
                    str(type(tag))
                )
            )
            
        self.assertTrue(
            'submit_form' in context,
            msg='Expecting the context for taggable to contain \'submit_form\' attribute'
        )
        
        self.assertTrue(
            'tagged_item' in context,
            msg='Expecting the context for taggable to contain \'tagged_item\' attribute'
        )
        
        self.assertTrue(
            isinstance(context['tagged_item'], Taggable),
            msg='Expecting the tagged_item to be an instance of %s' % (
                str(Taggable)
            )
        )
        
        self.assertTrue(
            'submit_url' in context,
            msg='Expecting the context for taggable to contain \'submit_url\' attribute'
        )
        
        self.assertTrue(
            type(context['submit_url']) == str,
            msg='Expecting submit_url to be of type string'
        )
        
        self.assertTrue(
            'delete_url_name' in context,
            msg='Expecting the context for taggable to contain \'delete_url_name\' attribute'
        )
        
        self.assertTrue(
            type(context['delete_url_name']) == str,
            msg='Expecting delete_url_name to be of type string'
        )
        
class MapableViewTestCaseMixin:
    
    def assertMapableContext(self, context):
        self.assertTrue(
            'all_points' in context,
            msg='Expecting the context for mapable point to contain \'all_points\' attribute'
        )
        
        for point in context['all_points']:
            self.assertTrue(
                isinstance(point, Mapable),
                msg='Expecting all entries to be an instance of %s, got %s' % (
                    str(Mapable),
                    str(type(point))
                )
            )
            
        self.assertTrue(
            'map_center' in context,
            msg='Expecting the context for mapable point to contain \'map_center\' attribute'
        )
        
        self.assertTrue(
            'latitude' in context['map_center'],
            msg='Expecting the map center to contain an \'latitude\' attribute'
        )
        
        self.assertTrue(
            isinstance(context['map_center']['latitude'], float) or isinstance(context['map_center']['latitude'], int),
            msg='Expecting the latitude of the map center to be numeric, type %s given' % (
                str(type(context['map_center']['latitude']))
            )
        )
        
        self.assertTrue(
            -90 <= context['map_center']['latitude'] <= 90,
            msg='Expecting the latitude of map center to be in the range of -90 and 90'
        )
        
        self.assertTrue(
            'longitude' in context['map_center'],
            msg='Expecting the map center to contain an \'longitude\' attribute'
        )
        
        self.assertTrue(
            isinstance(context['map_center']['longitude'], float) or isinstance(context['map_center']['longitude'], int),
            msg='Expecting the longitude of the map center to be numeric, type %s given' % (
                str(type(context['map_center']['longitude']))
            )
        )
        
        self.assertTrue(
            -180 <= context['map_center']['longitude'] <= 180,
            msg='Expecting the longitude of map center to be in the range of -180 and 180'
        )
            
        