import datetime, glob, json, os, sys

from django.contrib.auth.decorators import login_required, user_passes_test
from django.http import HttpResponse, Http404
from django.shortcuts import render_to_response
from django.template import RequestContext

from reportlab.graphics.renderPM import drawToPIL
from obj2json import getSample, obj2json
from utils import exception2response
import settings as chart_settings

_moduleName = 'chartGalleryTmpModule'

def galleryDir(galleryId):
    return os.path.join(chart_settings.GALLERIES_DIR,galleryId)

def galleryFilePath(galleryId,chartId):
    return os.path.join(chart_settings.GALLERIES_DIR,galleryId,chartId)

def makeModule(src,name=_moduleName):
    try:
        from imp import new_module
        module = new_module(name)
        exec src in module.__dict__
        return module
    except:
        pass

def isDrawing(d):
    from reportlab.graphics.shapes import Drawing
    try:
        return issubclass(d,Drawing)
    except:
        pass

def getDrawing(filePath):
    with open(filePath,'r') as f:
        src = f.read()
    m = makeModule(src)
    title = m.__doc__
    if m:
        try:
            A = [getattr(m,d) for d in dir(m)]
            D = [a for a in A if getattr(a,'__module__',None)==m.__name__ and isDrawing(a)]
            if len(D)==1:
                drawing = D[0]
                drawing.__implementation_module__ = m
                return drawing, title
        except:
            return None 

def chartGalleries():
    cg = getattr(chart_settings,'GALLERIES',None)
    if cg is None:
        chart_settings.GALLERIES = cg = [d for d in os.listdir(chart_settings.GALLERIES_DIR) if not d.startswith('.') and os.path.isfile(galleryFilePath(d, '__init__.py'))]
    return cg

def galleryManifests():
    gM = getattr(chart_settings,'GALLERY_MANIFESTS',None)
    if gM is None:
        chart_settings.GALLERY_MANIFESTS = gM = {}
    return gM

def galleryManifest(galleryId):
    gM = galleryManifests()
    M = gM.get(galleryId,None)
    if M is None:
        F = glob.glob(os.path.join(galleryDir(galleryId),'*.py'))
        F.remove(os.path.join(galleryDir(galleryId),'__init__.py'))
        F = [os.path.splitext(os.path.basename(fn))[0] for fn in F if getDrawing(fn)]
        F.sort()
        gM[galleryId] = M = F
    return M

def index(request):
    '''index page for all galleries
    '''
    d = dict() # context dict
    d['chart_galleries'] =  chartGalleries()
    d['content_html'] = '''
<p>This gallery gives examples of the numerous chart types one can construct using ReportLab's graphics tool-kit.</p>

<p>Each chart is a Python module which can be run directly from the command line, provided the necessary prerequisites are installed.</p>

<p>Our graphical editing tool, Diagra, can open these modules and allow their adjustment.</p>

<p>Browse the chart types on the left to find the one you're looking for. If it isn't there, give us a shout!</p>

'''
    d['page_title'] = 'ReportLab Chart Galleries'
    return render_to_response('chartgallery/index.html', RequestContext(request,d))

def gallery(request, galleryId):
    '''index for all charts in one gallery
    '''
    d = dict() # context dict
    d['chart_galleries'] =  chartGalleries()
    charts = galleryManifest(galleryId)
    d['charts'] = []
    try:
        with open(os.path.join(galleryDir(galleryId), "manifest.md")) as manifest:
            d['manifest'] = manifest.read()
    except:
        # there is no such file
        d['manifest'] = "[No gallery manifesto]"
    for chart in charts:
        try:
            draw, title = getDrawing(os.path.join(galleryDir(galleryId),chart+".py"))
        except IOError:
            raise Http404
        d['charts'].append((chart, thumbnail_url(galleryId, chart), title))
    d['gallery'] = galleryId
    d['help_message'] = 'Click on one of the charts on the left to browse their properties.'
    d['sc'] = {
            'THUMBNAIL_WIDTH': chart_settings.THUMBNAIL_WIDTH,
            'THUMBNAIL_HEIGHT': chart_settings.THUMBNAIL_HEIGHT,
            }
    return render_to_response('chartgallery/gallery.html', RequestContext(request,d))

def typeIndex(request, galleryId, chartType):
    return render_to_response('chartgallery/chartByType.html', {})

def chart(request, galleryId, chartId):
    '''returns an html page for a chart
    '''
    try:
        drawing, title = getDrawing(galleryFilePath(galleryId,chartId)+'.py')
    except IOError:
        raise Http404
    doc = drawing.__doc__ or ''
    if doc:
        doc = doc.split('\n')
        doc0 = doc[0]
        if len(doc) > 1:
            doc1 = doc[1:]
            nt = max(min([len(d)-len(d.lstrip('\t')) for d in doc1 if d.strip()]),
                min([len(d)-len(d.lstrip(' ')) for d in doc1 if d.strip()]))
            doc = '\n'.join([doc0] + [d[nt:] for d in doc1])
            #doc = doc.split('\n')
            #title = '\n'.join(doc[:3])
            #doc = '\n'.join(doc[3:])
        else:
            doc = doc0
    d = dict(
            doc=doc,
            cssid='chart',
            gallery=galleryId,
            charts = galleryManifest(galleryId),
            chart=chartId,
            title=title,
            chart_galleries=chartGalleries(),
            scale= float(request.GET.get('scale', chart_settings.DEFAULT_SCALE)),
            format=request.GET.get('format', chart_settings.DEFAULT_MIME),
            )
    assert d['format'] in chart_settings.VALID_FORMATS, 'Unknown file format "%s"' % d['format']
    assert chart_settings.MIN_SCALE <= d['scale'] <= chart_settings.MAX_SCALE, 'can not scale chart to %s, scale is limited between %s .. %s'%(d['scale'], chart_settings.MIN_SCALE, chart_settings.MAX_SCALE)
    if request.GET.get('iframe', False):
        template = 'chartgallery/chart_iframe.html'
    else:
        template = 'chartgallery/chart.html'
    return render_to_response('chartgallery/chart_iframe.html', RequestContext(request, d))


def thumbnail_url(galleryId, chartId):
    '''
    generate thumbnail url for the given gallery/chart
    check for up-to-date pre-existing ones
    '''
    module_file_path = galleryFilePath(galleryId,chartId)+'.py'
    mtime = os.stat(module_file_path).st_mtime
    if mtime:
        thumb_name = "%s_%s" % (chartId, datetime.datetime.fromtimestamp(mtime).strftime('%Y%m%d%H%M%S'))
        thumb_name_ext = "%s.%s" % (thumb_name,chart_settings.DEFAULT_MIME)
        thumb_path = os.path.join(chart_settings.CACHE_ROOT, galleryId)
        if not os.path.exists(os.path.join(thumb_path, thumb_name_ext)):
            if not os.path.isdir(thumb_path):
                os.makedirs(thumb_path)
            try:
                drawing, title = getDrawing(module_file_path)
            except IOError:
                raise Http404
            d = drawing()
            ini_width = d.width*1.0
            if ini_width > chart_settings.THUMBNAIL_WIDTH:
                scale = chart_settings.THUMBNAIL_WIDTH / ini_width
                d.width *= scale
                d.height *= scale
                d.transform = scale,0,0,scale,0,0
                #d = drawing()#width=chart_settings.THUMBNAIL_WIDTH, height=chart_settings.THUMBNAIL_HEIGHT)
            im = drawToPIL(d)
            wi, he = im.size
            x1 = (wi - chart_settings.THUMBNAIL_WIDTH)/2
            y1 = (he - chart_settings.THUMBNAIL_HEIGHT)/2
            if x1 < 0: 
                x1 = 0
            y2 = y1+chart_settings.THUMBNAIL_HEIGHT
            if y1 < 0: 
                y1 = 0
                y2 = he
            
            crop_box = (x1, y1, x1+chart_settings.THUMBNAIL_WIDTH, y2)
            pa = im.crop(crop_box)
            pa.save(os.path.join(thumb_path, thumb_name_ext), chart_settings.DEFAULT_MIME.upper())
            del im, pa
        return "%s%s/%s" % (chart_settings.CACHE_URL, galleryId, thumb_name_ext)
    return None


@exception2response
def chartImage(request, galleryId, chartId):
    '''returns a visual representation of a chart
    you can specify scale and format as GET parameters
    if format is not specified chart_settings.DEFAULT_MIME will be used
    '''
    drawing, title = getDrawing(galleryFilePath(galleryId,chartId)+'.py')
    d = drawing()   # as iobtained dynamically above
    safe_dict = dict([(k,None) for k in dir(__builtins__)])
    data = request.GET.get('data', None)
    if data:
        #data = [n for n in [l.split(',') for l in data.split('|')]]
        try:
            d.chart.data = eval(data, safe_dict)
        except:
            print 'invalid data'
    names = request.GET.get('names', None)
    if names:
        print 'names= %r'%names
        try:
            d._seriesNames = eval(names, safe_dict)
        except:
            print 'eval failed'
    sf = float(request.GET.get('scale', chart_settings.DEFAULT_SCALE))
    if sf > chart_settings.MAX_SCALE or sf < chart_settings.MIN_SCALE:
        raise ValueError('can not scale chart to %s, scale is limited between %s .. %s'%(sf, chart_settings.MIN_SCALE, chart_settings.MAX_SCALE))
    d.width*=sf
    d.height*=sf
    d.transform=sf,0,0,sf,0,0
    format = request.GET.get('format', chart_settings.DEFAULT_MIME) # try pdf, png, etc
    assert format in chart_settings.VALID_FORMATS, 'Unknown file format "%s"' % format
    if format in ('pdf', 'eps', 'ps'):
        m = 'application/%s'%format
    elif format == 'py':
        m = 'text/plain'
    elif format == 'svg':
        m = 'image/svg+xml'
    else:
        m = 'image/%s'%format

    #get a GIF (or PNG, JPG, or whatever)
    binaryStuff = d.asString(format)
    return HttpResponse(binaryStuff, m)

def chartAttrs(request, galleryId, chartId):
    '''returns a json representation of all chart attributes
    '''
    drawing, title = getDrawing(galleryFilePath(galleryId,chartId)+'.py')
    d =  drawing()
    j = obj2json(d)
    return HttpResponse(j,mimetype='application/javascript')

    
