"""
Brochure utilities..
"""
import cStringIO
import os
import json
from hashlib import md5
from django.conf import settings
from django.template.loader import render_to_string
from django.template import loader, TemplateDoesNotExist
from PIL import Image
from rlextra.rml2pdf import rml2pdf

from django.utils.encoding import smart_str
from xml.sax.saxutils import escape
from django.utils.safestring import SafeString, SafeUnicode
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.shortcuts import (render_to_response, HttpResponse, HttpResponseRedirect)
from django.contrib.sites.models import Site
from django.core.mail.message import EmailMessage



import rlextra
from rlextra.preppy import preppy

from models import Section, Pdf, Subset

def binderEscape(s):
    if isinstance(s,(SafeString,SafeUnicode)):
        return smart_str(s)
    else:
        return escape(smart_str(s))

def get_template_choices(template_name, app_name='binder'):
    """
    Shortcut for generating template choices.
    """
    template_choices = [
        '%s/%s' % (app_name, template_name)
        ]

    return template_choices


def make_pdf(rml, save_pdf=False):
    """
    Shortcut for creating pdf. If DEBUG is saves last rml and pdf to disk
    """
    if settings.DEBUG:
        rml_file = open(settings.LAST_RML, 'wb')
        rml_file.write(rml)
        rml_file.close()

    buf = cStringIO.StringIO()
    rml2pdf.go(rml, outputFileName=buf)
    buf.reset()
    pdf_data = buf.read()
    if settings.DEBUG or save_pdf:
        pdf_file = open(settings.LAST_PDF, 'wb')
        pdf_file.write(pdf_data)
        pdf_file.close()

    return pdf_data


def get_brochure_template(pdf_name=None):
    """
    Shortcut for getting brochure template.
    """
    template = getattr(settings, 'brochure_template', 'brochure.rml')
    if pdf_name:
        try:
            #if the first line succeeds, there is a document-specific template
            loader.get_template('binder/%s/%s' % (pdf_name, template))
            template = '%s/%s' % (pdf_name, template)
        except TemplateDoesNotExist:
            pass
    return get_template_choices(template)

def get_brochure_template_path(pdf_name,template_name=None):
    """
    Returns path to the correct template (3 paths, returns first one it finds).
    If template name isn't supplied it returns the path to 'brochure.prep'
    """
    
    if not template_name: 
        template_name = getattr(settings, 'brochure_template', 'brochure.prep')
    
    p='/'.join([settings.PROJECT_DIR ,'brochures/templates/binder',pdf_name, template_name])

    if os.path.isfile(p):
        return p
    else:
        p='/'.join([settings.PROJECT_DIR ,'brochures/templates/binder', template_name])
        if os.path.isfile(p):
            return p
        else:
            p='/'.join([rlextra.__path__[0] ,'django/apps/binder/templates/binder', template_name])
            return p

def generate_single_pdf_brochure(pdf_name=None, sections=None, missing_sections=None, save_pdf=False, extra={}):
    """
    Used for generating pdf brochure.
    """
    # pylint: disable-msg=E1101
    if isinstance(sections, list):
        s = []
        for i in sections:
            s.append(i.id)
        sections = s
    
    if sections:
        sections = Section.objects.filter(id__in=sections)
    elif pdf_name:
        sections = Section.objects.filter(pdf__name=pdf_name)
    else:
        raise ValueError("generate_single_pdf_brochure requires either a PDF document name, or a list of section IDs")
        
    #often we're given the section IDs and not the PDF name.  But we can always infer it.
    pdf = sections[0].pdf
    
    if missing_sections:
        missing_sections = Section.objects.filter(id__in=missing_sections)
    else:
        missing_sections = []
    # pylint: enable-msg=E1101        

    # TODO: move to settings ?
    normal_font = 'Helvetica'
    bold_font = 'Helvetica-Bold'

    pdf_page_numbers = []
    if pdf.cover:
        for page_num in pdf.cover.get_pdf_page_numbers():
                pdf_page_numbers.append(page_num)
    previous = None
    for section in sections:
        section.previous = previous
        previous = section
        if not section.abstract:
            for page_num in section.get_pdf_page_numbers():
                pdf_page_numbers.append(page_num)
    pdf_page_numbers = ' '.join([str(p) for p in pdf_page_numbers])

    #we don't want to repeat the cover.  If the cover is included
    #in the section list, remove it. 
    
    if sections[0] == pdf.cover:
        sections = sections[1:]

    nameSpace={
        'pdf': pdf,
        'sections': sections,
        'pdf_page_numbers': pdf_page_numbers,
        'missing_sections': missing_sections,
        'width': sections[0].pdf.width,
        'height': sections[0].pdf.height,
        'extra': extra,
        'MEDIA_ROOT': settings.MEDIA_ROOT,
        'PROJECT_DIR': settings.PROJECT_DIR,
        }

    
    templ=get_brochure_template_path(pdf_name)
    modul = preppy.getModule(templ)
    
    rml = modul.getOutput(nameSpace, quoteFunc=binderEscape)
    pdf_data = make_pdf(rml, save_pdf=save_pdf)
    return pdf_data
    
def generate_all_pdf_brochure(pdf_name='all.pdf', sections=[], first_pdf=None, save_pdf=False, extra={}):
    """
    Used for generating pdf brochure with sections selected from all pdfs.
    """
    pdf_page_numbers = {}
    previous = None
    for section in sections:
        section.previous = previous
        previous = section
        if section.pdf.slug not in pdf_page_numbers:
            pdf_page_numbers[section.pdf.slug] = {'page_numbers': [], 'pdf': section.pdf}
        if not section.abstract:
            for page_num in section.get_pdf_page_numbers():
                pdf_page_numbers[section.pdf.slug]['page_numbers'].append(page_num)
    
    for page_num in first_pdf.cover.get_pdf_page_numbers():
        pdf_page_numbers[first_pdf.slug]['page_numbers'].append(page_num)
    
    pdf_pages_list = []
    for slug, pdf_pages in pdf_page_numbers.items():
        pages_string = ' '.join([str(p) for p in pdf_pages['page_numbers']])
        pdf_pages_list.append({'pdf': pdf_pages['pdf'], 'page_numbers': pages_string})
    
    nameSpace={
        'first_pdf': first_pdf,
        'sections': sections,
        'pdf_pages_list': pdf_pages_list,
        'width': sections[0].pdf.width,
        'height': sections[0].pdf.height,
        'extra': extra,
        'MEDIA_ROOT': settings.MEDIA_ROOT,
        'PROJECT_DIR': settings.PROJECT_DIR,
        }

    templ=get_brochure_template_path('all_courses')
    modul = preppy.getModule(templ)
    rml = modul.getOutput(nameSpace, quoteFunc=binderEscape)
    rml2pdf._saveLastestRml(os.path.join(settings.PROJECT_DIR, 'last.rml'), rml)
    pdf_data = make_pdf(rml, save_pdf=save_pdf)
    return pdf_data

def generate_flipbook_js(sections=None, pdf_url=None):
    """
    Used for generating pdf brochure.
    """
    if not sections:
        sections = Section.objects.all() # pylint: disable-msg=E1101

    images = []
    sizes = {}
    contents = []
    page = 2 #TODO: Maybe this should be dynamically determined from somewhere?
    for section in sections:
        if not section.abstract:
            images.extend(section.get_flipbook_pages())
            contents.append((section.name, page))
            page += section.get_page_count()

    if sections[0].pdf.cover:
        cover_pages = sections[0].pdf.cover.get_flipbook_pages()
    else:
        cover_pages = []
        
    im = Image.open(os.path.join(settings.SECTIONS_ROOT, images[0]))
    sizes['normal'] = im.size
    im = Image.open(os.path.join(settings.SECTIONS_ROOT, 'large', images[0]))
    sizes['large'] = im.size

    js_data = render_to_string(get_template_choices('brochure.js'), {
        'cover_pages': cover_pages,
        'contents': contents,
        'images': images,
        'sizes': sizes,
        'MEDIA_URL': settings.MEDIA_URL,
        'pdf_url': pdf_url,
        }).encode('utf-8')

    return js_data

def get_sections_string(sections, pdf_id):
    """
    Passed a string of section ids/slugs, returns list of relevant section objects
    """
    s2=[]
    if isinstance(sections, (str, unicode)):
        for i in sections.replace(' ', '').split(','):
            try:
                s2.append(Section.objects.get(id=int(i), pdf=pdf_id))
            except:
                try:
                    s2.append(Section.objects.get(slug=str(i), pdf=pdf_id))
                except:
                    pass
    return s2
    

def get_dependant_sections(sections=[], pdf_id=None):
        """
        Takes a list of section objects or a string consisting of section ids and/or slugs separated by commas.
        If any of the sections are abstract, it adds in all the relevant subsections.
        If any of the sections are not abstract, it adds in the parent abstract sections.
        """
        # check if we've been passed a string of section ids/slugs
        # if so, get the relevant objects
        if isinstance(sections, (str, unicode)):
            sections=get_sections_string(sections, pdf_id)
        
        if sections and len(sections):
            ss=list(sections)
            ss=sorted(ss,key=lambda blah: blah.order)
            sections=list(sections)
            sections=sorted(sections,key=lambda blah: blah.order)

            f=False
            if not pdf_id:
                pdf_id = sections[0].pdf_id
            # this bit selects all the subsections of any abstract sections in the list
            everything=Section.objects.filter(pdf__id=pdf_id).order_by('order')
            for i in sections:
                if i.abstract:
                    for j in everything:
                        if f:
                            ss.append(j)
                        if f and j.abstract:
                            ss.pop()
                            f=False
                            break
                        if j.id==i.id:
                            f=True
            # now we have to find the relevant parent abstract section
            # for any non-abstract sections in the list
            additional_parents = []
            for s in ss:
                parent_abstract = None
                current_found = False
                if not s.abstract:
                    for e in everything:
                        if not current_found:
                            if e.abstract:
                                parent_abstract = e
                            elif e.id == s.id:
                                current_found = True
                    if parent_abstract and not parent_abstract in ss and not parent_abstract in additional_parents:
                        additional_parents.append(parent_abstract)
                        
            ss.extend(additional_parents)
            ss=sorted(ss,key=lambda blah: blah.order)
            return ss
        else:
            return []
def get_mandatory_sections(pdf_id,sections):
    """
    give it a pdf id and a list of sections.  It will add any mandatory sections to the list and return it. 
    """

    if isinstance(sections, (str, unicode)):
            sections=get_sections_string(sections, pdf_id)

    mss=Section.objects.filter(pdf=pdf_id,mandatory=True)
    for ms in mss:
        sections.append(ms)

    return sections

def deliver_pdf(filename, pdf_name, disposition='inline', email_to=None, subject='', body='', email_from='', request=None, api=False, output_filename=None):

    ps=getparams_to_string(request)
    pdf_data = open(filename, 'r').read()
    if output_filename: output_filename+='_brochure.pdf'
    if disposition == 'email':
        if email_from and not subject:
            subject = '%s has sent you an eBrochure.' % email_from
        elif not subject:
            subject = 'You have been sent an eBrochure.'
        if email_from and not body:
            body= ('%s has sent you an eBrochure.' % email_from)+ '\n' + body
        elif not body:
            body='Your eBrochure is attached.'
            
        email_message = EmailMessage(
            subject=subject,
            body=body,
            from_email='noreply@reportlab.com',
            to=email_to,
            )
        email_message.attach((output_filename or 'brochure.pdf'), pdf_data, 'application/pdf')
        email_message.send()
        if request:
            messages.info(
                request,
                ('Your ebrochure has been successfully sent to: %s.' %
                 ', '.join(email_to))
                )
        if not api:
            return HttpResponseRedirect(
                reverse('binder_download', args=[pdf_name, filename.split('/')[-1][:-4]])+ps
            )
        else:
            return HttpResponse('Your eBrochure email has been sent.', mimetype='text/plain; charset=utf-8')
    
    elif disposition == 'flip_book':
        return HttpResponseRedirect(
            reverse('binder_flipbook', args=[pdf_name, filename.split('/')[-1][:-4]])
        )
    
    elif disposition == 'attachment' or disposition=='inline' or disposition=='download':
        if disposition == 'attachment':
            response = HttpResponse(mimetype='application/pdf')
            response['Content-Disposition'] = ('attachment; filename=%s' % (output_filename or 'brochure.pdf'))
        else:
            response = HttpResponse(mimetype='application/pdf')
            response['Content-Disposition'] = ('inline; filename=%s' % (output_filename or 'brochure.pdf'))
        response['Content-Length'] = len(pdf_data)
        response.write(pdf_data)
        return response
    
    elif disposition=='url':
        return HttpResponse('http://%s%s/%s' % (Site.objects.get_current(), settings.OUTPUT_URL, filename.split('/')[-1]), mimetype='text/plain; charset=utf-8')
    
    elif disposition=='html':
        html='<html><body><a href="%s/%s">Click here to download your eBrochure.</a></body></head>' % (settings.OUTPUT_URL, filename.split('/')[-1])
        return HttpResponse(html)


def get_pdf_md5(pdf_id, request):
    """
    Returns whether the PDF is in the cache and the MD5 sum of the document. 
    """
    cached = False
    text = [str(pdf_id),]
    request_params = dict(request.REQUEST)
    
    # Make a list of all the used sections as this makes up most of the document uniqueness
    # Section parameters as passed in from the create form: ?section_1=on&section_3=on etc.
    form_sections = []
    for key, val in request_params.items():
        if key[:8] == 'section_' and val == 'on':
            form_sections.append(key[8:])
            request_params.pop(key)
        # Add in sections that are in a subset
        if key == 'subset':
            for sec in Subset.objects.get(slug=val).sections.all():
                form_sections.append(sec.slug)
    
    form_sections = get_sections_string(','.join(form_sections), pdf_id)
    
    # Section ids in a single string as used in the API: ?section=1,slug-2,3,4,5 etc.
    multi_sections = []
    if 'sections' in request_params:
        multi_sections = get_sections_string(request_params['sections'], pdf_id)
        request_params.pop('sections')
    
    # Join up the two types of section lists, remove duplicates and sort
    sections = list(set(form_sections).union(set(multi_sections)))
    sections = sorted(sections,key=lambda blah: blah.order)
    
    # Add all included section id and their timestamps
    for section in sections:
        date = '0'
        if section.updated:
            date = section.updated.strftime('%s')
        text.append('%s%s' % (date, section.id))
    
    if 'subset' in request_params:
        subset = Subset.objects.get(slug=request_params['subset'])
        text.append('subset=%s%s' % (subset.modified.strftime('%s'), request_params['subset']))
        request_params.pop('subset')
    
    if os.path.isfile(os.path.join(settings.PROJECT_DIR, 'version.txt')):
        text.append(str(os.stat(os.path.join(settings.PROJECT_DIR, 'version.txt'))))
    
    # Remove cross-site request forgery token as this will always be unique
    if 'csrfmiddlewaretoken' in request_params:
        request_params.pop('csrfmiddlewaretoken')
    
    # Add all other submitted parameters to the MD5 sum text
    for key, val in request_params.items():
        text.append('%s=%s' % (key, val))
    
    text = ','.join(text)
    pdf_md5 = md5(text).hexdigest()

    if os.path.isfile(Pdf.objects.get(id=pdf_id).get_pdf_path(pdf_md5)):
        cached = True

    return cached, pdf_md5

def getparams_to_string(request):

    ps='?'
    if request and request.GET.get:
        for i,j in request.GET.items():
            ps+=i+'='+j+'&'
    ps=ps.rstrip('&?')

    return ps
