from __future__ import with_statement
from __future__ import division

import tempfile, os, re
import mimetypes
import subprocess

from . import formats
from .dependencies import Dependencies

class Intermed(object):
    def __init__(self,metadata={}):
        self.md = metadata
        self.path = None
        self.extension = None
        self.data = None
        self.deps = Dependencies()
        self.cached = False
    def isPath(self):
        return self.path is not None
    def asPath(self):
        if self.path is None:
            assert self.extension.startswith('.'),self.extension
            fdesc,self.path = tempfile.mkstemp(suffix=self.extension)
            os.write(fdesc,self.data)
            os.close(fdesc)
        return self.path
    def asData(self):
        if self.data is None:
            with open(self.path) as f:
                self.data = f.read()
            self.nix()
        return self.data
    def getExtension(self):
        return self.extension
    def nix(self):
        if self.path is not None:
            os.remove(self.path)
            self.path = None
    def metadata(self):
        return self.md

    def addPropvalDep(self, elm, propname):
        self.deps.addPropvalDep(elm, propname)
    def addDeps(self,deps):
        self.deps.update(deps)
    def getDeps(self):
        return self.deps

    def setData(self,data,extension):
        self.nix()
        self.extension = unicode(extension)
        self.data = data
        self.path = None
        return self
    def setPath(self,path):
        self.nix()
        self.path = path
        self.extension = u'.'+path.rsplit('.',1)[-1]
        self.data = None

PIXELS_PER_IN = 96

class Length(object):
    def __init__(self, length, unit):
        self.length = length
        self.unit = unit
    def to_px(self):
        if self.unit == 'in':
            return int(PIXELS_PER_IN * self.length)
        else:
            assert False, (self.length, self.unit)
    def to_str(self):
        return "%s%s" % (self.length, self.unit)

def to_dim(s):
    if s == '?':
        return None
    elif s.endswith('in'):
        return Length(float(s[:-2]), 'in')
    else:
        return int(s)
def parse_dimension(arg):
    if arg.startswith('!'):
        arg = arg[1:]
        force=True
    else:
        force=False
    if 'x' in arg:
        w,h = (to_dim(b) for b in arg.split('x'))
    else:
        w = h = int(arg)
    return w,h,force

def parse_dimension_px(arg):
    w, h, force = parse_dimension(arg)
    if hasattr(w, 'to_px'):
        w = w.to_px()
    if hasattr(h, 'to_px'):
        h = h.to_px()
    return w, h, force

def rsvg_convert(im,format='.png'):
    args = []
    force = False
    if 'd' in im.metadata()['filters']:
        w,h,force = parse_dimension_px(im.metadata()['filters']['d'])
        del im.metadata()['filters']['d']
        # This takes width and height in pt, where 1pt=1.25px, and as integers.
        # But only when converting to .svg, to be confusing.
        if format == '.svg':
            mw = int(w/1.25)
            mh = int(h/1.25)
        else:
            mw = w
            mh = h
        if force:
            args.append('-w')
            args.append(str(mw))
            args.append('-h')
            args.append(str(mh))
        else:
            ow, oh = formats.rsvg_dimensions(im)
            # We need to pass just one -w or -h.  If we pass both, it makes
            # it at least that large with -a, but we want at most that large.
            args.append('-a')
            if ow / oh > w / h:
                args.append('-w')
                args.append(str(mw))
                h = int(oh / ow * w)
            else:
                args.append('-h')
                args.append(str(mh))
                w = int(ow / oh * h)
    else:
        w = h = None
    args.append('-f')
    args.append(format[1:])

    if format == '.pdf':
        # For PDF we have to output to a file and then convert to PDF 1.3,
        # otherwise XeTeX barfs.
        fdesc, pdftmp = tempfile.mkstemp(suffix='.pdf')
        os.close(fdesc)
        args.append('-o')
        args.append(pdftmp)
    
    p=subprocess.Popen(['rsvg-convert']+args+[im.asPath()],
                       stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    out,err = p.communicate()
    if p.returncode != 0:
        # Error messages get to stdout, which is dumb.
        raise OSError(
            "rsvg-convert failed with status %s: %s" % (p.returncode, out+err))
    if format == '.pdf':
        fdesc, pdf13tmp = tempfile.mkstemp(suffix='.pdf')
        os.close(fdesc)
        # Works to convert pdfs, too.
        p2 = subprocess.Popen(['ps2pdf13', pdftmp, pdf13tmp],
                              stderr=subprocess.PIPE)
        out, err = p2.communicate()
        if p2.returncode != 0:
            raise OSError("ps2pdf13 failed with status %s: %s" % (p2.returncode,
                                                                  err))
        os.unlink(pdftmp)
        im.setPath(pdf13tmp)
    else:
        im.setData(out,format)
    if w is not None:
        im.metadata()['width'] = w
    if h is not None:
        im.metadata()['height'] = h

FORMAT_TO_PSTOEDIT = {'.svg' : 'plot-svg',
                      '.eps' : 'ps',
                      '.epdf': 'gs:pdfwrite',
                      }

def pstoedit_convert(im, format):
    assert format in FORMAT_TO_PSTOEDIT
    cmd = ['pstoedit', '-f', FORMAT_TO_PSTOEDIT[format],
           '-pti',
           im.asPath()]
    p=subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = p.communicate()
    if p.returncode != 0:
        raise OSError(p.returncode, err)
    im.setData(out, format)

def get_pdfinfo(im):
    from pyPdf import PdfFileReader
    pdf = PdfFileReader(open(im.asPath(), 'rb'))
    im.setData("%s pages" % pdf.getNumPages(), 'pdfinfo')

# TRANSLATORS maps dest to a map that maps source to function.
# So, if TRANSLATORS['.pdf']['.tex'] is func, then func takes
# an Intermed that refers to '.tex' formatted data and sets
# that Intermed with '.pdf' formatted data or a appropriate path.
#
# Formats can start with a '.', in which case they represent complete
# files, or without a '.', in which case they represent incomplete
# fragments.
#
# A translator should either call asData or asPath, whichever is more
# convenient, but only once.
#
# Anything that calls asPath but doesn't call setData or setPath
# should call nix when it's done with the file at that path.
TRANSLATORS = {'.txt': {'txt':lambda im: im.setData(im.data.encode('utf-8'),
                                                    '.txt')},
               '.png': {'.svg':lambda im:rsvg_convert(im, '.png')},
               '.epdf': {'.svg':lambda im:rsvg_convert(im, '.pdf')},
               '.eps': {'.svg':lambda im:rsvg_convert(im, '.eps')},
               'pdfinfo': {'.pdf': get_pdfinfo},
               }

# Need a helper function b/c python doesn't freeze the environment of a lambda
def _trans(func, format):
    def translator(im):
        return func(im, format)
    return translator

for to_f in FORMAT_TO_PSTOEDIT:
    for from_f in formats.PSTOEDIT_READABLE_FORMATS:
        if to_f != from_f:
            if from_f not in TRANSLATORS.setdefault(to_f, {}):
                TRANSLATORS[to_f][from_f] = _trans(pstoedit_convert,
                                                   to_f)

# Overrides for mimetypes.guess_extension
MIME_MAP = {"image/svg+xml": ".svg",
            "text/plain": ".txt",
            "application/x-empty": ".txt",
            "text/x-c": ".txt",
            "text/html": ".html",
            "image/jpeg": ".jpg"}

# Overrides for mimetypes.guess_type
EXT_MAP = {'.svg': 'image/svg+xml',
           '.tex': 'text/plain'}

def guess_extension(mime):
    if mime in MIME_MAP:
        return MIME_MAP[mime]
    else:
        ext = mimetypes.guess_extension(mime)
        #assert ext is not None,mime
        if ext is None:
            return ".dat"
        return ext

def guess_type(filename):
    ext = '.' + filename.rsplit('.', 1)[-1]
    if ext in EXT_MAP:
        return EXT_MAP[ext], None
    return mimetypes.guess_type(filename, strict=False)
