#!/usr/bin/env python
# -*- coding: utf-8 -*-
##############################################################################
#    
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2004-2009, P. Christeas, Tiny SPRL (<http://tiny.be>).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.     
#
##############################################################################

import sys
import StringIO
from lxml import etree

import utils

Font_size= 10.0

def verbose(text):
    sys.stderr.write(text+"\n")


class textbox(object):
    """A box containing plain text.
    It can have an offset, in chars.
    Lines can be either text strings, or textbox'es, recursively.
    """
    def __init__(self,x=0, y=0):
        self.posx = x
        self.posy = y
        self.lines = []
        self.curline = ''
        self.endspace = False
     
    def newline(self):
        if isinstance(self.curline, textbox):
            self.lines.extend(self.curline.renderlines())
        else:
            self.lines.append(self.curline)
        self.curline = ''
    
    def fline(self):
        if isinstance(self.curline, textbox):
            self.lines.extend(self.curline.renderlines())
        elif len(self.curline):
            self.lines.append(self.curline)
        self.curline = ''
    
    def appendtxt(self,txt):
        """Append some text to the current line.
           Mimic the HTML behaviour, where all whitespace evaluates to
           a single space """
        if not txt:
            return
        bs = es = False
        if txt[0].isspace():
            bs = True
        if txt[len(txt)-1].isspace():
            es = True
        if bs and not self.endspace:
            self.curline += " "
        self.curline += txt.strip().replace("\n"," ").replace("\t"," ")
        if es:
            self.curline += " "
        self.endspace = es

    def rendertxt(self,xoffset=0):
        result = ''
        lineoff = ""
        for i in range(self.posy):
            result +="\n"
        for i in range(self.posx+xoffset):
            lineoff+=" "
        for l in self.lines:
            result+= lineoff+ l +"\n"
        return result
    
    def renderlines(self,pad=0):
        """Returns a list of lines, from the current object
        pad: all lines must be at least pad characters.
        """
        result = []
        lineoff = ""
        for i in range(self.posx):
            lineoff+=" "
        for l in self.lines:
            lpad = ""
            if pad and len(l) < pad :
                for i in range(pad - len(l)):
                    lpad += " "
                #elif pad and len(l) > pad ?
                result.append(lineoff+ l+lpad)
        return result
            
            
    def haplines(self,arr,offset,cc= ''):
        """ Horizontaly append lines 
        """
        while len(self.lines) < len(arr):
            self.lines.append("")
        
        for i in range(len(self.lines)):
            while len(self.lines[i]) < offset:
                self.lines[i] += " "
        for i in range(len(arr)):
            self.lines[i] += cc +arr[i] 
        

class _flowable(object):
    def __init__(self, template, doc,localcontext):
        self._tags = {
            '1title': self._tag_title,
            '1spacer': self._tag_spacer,
            'para': self._tag_para,
            'font': self._tag_font,
            'section': self._tag_section,
            '1nextFrame': self._tag_next_frame,
            'blockTable': self._tag_table,
            '1pageBreak': self._tag_page_break,
            '1setNextTemplate': self._tag_next_template,
        }
        self.template = template
        self.doc = doc
        self.localcontext = localcontext
        self.nitags = []
        self.tbox = None

    def warn_nitag(self,tag):
        if tag not in self.nitags:
            verbose("Unknown tag \"%s\", please implement it." % tag)
            self.nitags.append(tag)
    
    def _tag_page_break(self, node):
        return "\f"

    def _tag_next_template(self, node):
        return ''

    def _tag_next_frame(self, node):
        result=self.template.frame_stop()
        result+='\n'
        result+=self.template.frame_start()
        return result

    def _tag_title(self, node):
        node.tagName='h1'
        return node.toxml()

    def _tag_spacer(self, node):
        length = 1+int(utils.unit_get(node.get('length')))/35
        return "\n"*length

    def _tag_table(self, node):
        self.tb.fline()
        saved_tb = self.tb
        self.tb = None
        sizes = None
        if node.get('colWidths'):
            sizes = map(lambda x: utils.unit_get(x), node.get('colWidths').split(','))
        trs = []
        for n in utils._child_get(node,self):
            if n.tag == 'tr':
                tds = []
                for m in utils._child_get(n,self):
                    if m.tag == 'td':
                        self.tb = textbox()
                        self.rec_render_cnodes(m)
                        tds.append(self.tb)
                        self.tb = None
                if len(tds):
                    trs.append(tds)
        
        if not sizes:
            verbose("computing table sizes..")
        for tds in trs:
            trt = textbox()
            off=0
            for i in range(len(tds)):
                p = int(sizes[i]/Font_size)
                trl = tds[i].renderlines(pad=p)
                trt.haplines(trl,off)
                off += sizes[i]/Font_size
            saved_tb.curline = trt
            saved_tb.fline()
        
        self.tb = saved_tb
        return

    def _tag_para(self, node):
        #TODO: styles
        self.rec_render_cnodes(node)
        self.tb.newline()

    def _tag_section(self, node):
        #TODO: styles
        self.rec_render_cnodes(node)
        self.tb.newline()

    def _tag_font(self, node):
        """We do ignore fonts.."""
        self.rec_render_cnodes(node)

    def rec_render_cnodes(self,node):
        self.tb.appendtxt(utils._process_text(self, node.text or ''))
        for n in utils._child_get(node,self):
            self.rec_render(n)
        self.tb.appendtxt(utils._process_text(self, node.tail or ''))

    def rec_render(self,node):
        """ Recursive render: fill outarr with text of current node
        """
        if node.tag is not None:
            if node.tag in self._tags:
                self._tags[node.tag](node)
            else:
                self.warn_nitag(node.tag)

    def render(self, node):
        self.tb= textbox()
        #result = self.template.start()
        #result += self.template.frame_start()
        self.rec_render_cnodes(node)
        #result += self.template.frame_stop()
        #result += self.template.end()
        result = self.tb.rendertxt()
        del self.tb
        return result

class _rml_tmpl_tag(object):
    def __init__(self, *args):
        pass
    def tag_start(self):
        return ''
    def tag_end(self):
        return False
    def tag_stop(self):
        return ''
    def tag_mergeable(self):
        return True

class _rml_tmpl_frame(_rml_tmpl_tag):
    def __init__(self, posx, width):
        self.width = width
        self.posx = posx
    def tag_start(self):
        return "frame start"
    def tag_end(self):
        return True
    def tag_stop(self):
        return "frame stop"
    def tag_mergeable(self):
        return False

    # An awfull workaround since I don't really understand the semantic behind merge.
    def merge(self, frame):
        pass

class _rml_tmpl_draw_string(_rml_tmpl_tag):
    def __init__(self, node, style):
        self.posx = utils.unit_get(node.get('x'))
        self.posy =  utils.unit_get(node.get('y'))
        aligns = {
            'drawString': 'left',
            'drawRightString': 'right',
            'drawCentredString': 'center'
        }
        align = aligns[node.localName]
        self.pos = [(self.posx, self.posy, align, utils.text_get(node), style.get('td'), style.font_size_get('td'))]

    def tag_start(self):
        return "draw string \"%s\" @(%d,%d)..\n" %("txt",self.posx,self.posy)

    def merge(self, ds):
        self.pos+=ds.pos

class _rml_tmpl_draw_lines(_rml_tmpl_tag):
    def __init__(self, node, style):
        coord = [utils.unit_get(x) for x in utils.text_get(node).split(' ')]
        self.ok = False
        self.posx = coord[0]
        self.posy = coord[1]
        self.width = coord[2]-coord[0]
        self.ok = coord[1]==coord[3]
        self.style = style
        self.style = style.get('hr')

    def tag_start(self):
        return "draw lines..\n"

class _rml_stylesheet(object):
    def __init__(self, stylesheet, doc):
        self.doc = doc
        self.attrs = {}
        self._tags = {
            'fontSize': lambda x: ('font-size',str(utils.unit_get(x))+'px'),
            'alignment': lambda x: ('text-align',str(x))
        }
        result = ''
        for ps in stylesheet.findall('paraStyle'):
            attr = {}
            attrs = ps.attributes
            for i in range(attrs.length):
                name = attrs.item(i).localName
                attr[name] = ps.get(name)
            attrs = []
            for a in attr:
                if a in self._tags:
                    attrs.append("%s:%s" % self._tags[a](attr[a]))
            if len(attrs):
                result += "p."+attr['name']+" {"+'; '.join(attrs)+"}\n"
        self.result = result

    def render(self):
        return ''

class _rml_draw_style(object):
    def __init__(self):
        self.style = {}
        self._styles = {
            'fill': lambda x: {'td': {'color':x.get('color')}},
            'setFont': lambda x: {'td': {'font-size':x.get('size')+'px'}},
            'stroke': lambda x: {'hr': {'color':x.get('color')}},
        }
    def update(self, node):
        if node.localName in self._styles:
            result = self._styles[node.localName](node)
            for key in result:
                if key in self.style:
                    self.style[key].update(result[key])
                else:
                    self.style[key] = result[key]
    def font_size_get(self,tag):
        size  = utils.unit_get(self.style.get('td', {}).get('font-size','16'))
        return size

    def get(self,tag):
        if not tag in self.style:
            return ""
        return ';'.join(['%s:%s' % (x[0],x[1]) for x in self.style[tag].items()])

class _rml_template(object):
    def __init__(self, localcontext, out, node, doc, images=None, path='.', title=None):
        self.localcontext = localcontext
        self.frame_pos = -1
        self.frames = []
        self.template_order = []
        self.page_template = {}
        self.loop = 0
        self._tags = {
            'drawString': _rml_tmpl_draw_string,
            'drawRightString': _rml_tmpl_draw_string,
            'drawCentredString': _rml_tmpl_draw_string,
            'lines': _rml_tmpl_draw_lines
        }
        self.style = _rml_draw_style()
        for pt in node.findall('pageTemplate'):
            frames = {}
            id = pt.get('id')
            self.template_order.append(id)
            for tmpl in pt.findall('frame'):
                posy = int(utils.unit_get(tmpl.get('y1'))) #+utils.unit_get(tmpl.get('height')))
                posx = int(utils.unit_get(tmpl.get('x1')))
                frames[(posy,posx,tmpl.get('id'))] = _rml_tmpl_frame(posx, utils.unit_get(tmpl.get('width')))
            for tmpl in node.findall('pageGraphics'):
                for n in tmpl.getchildren():
                    if n.nodeType==n.ELEMENT_NODE:
                        if n.localName in self._tags:
                            t = self._tags[n.localName](n, self.style)
                            frames[(t.posy,t.posx,n.localName)] = t
                        else:
                            self.style.update(n)
            keys = frames.keys()
            keys.sort()
            keys.reverse()
            self.page_template[id] = []
            for key in range(len(keys)):
                if key>0 and keys[key-1][0] == keys[key][0]:
                    if type(self.page_template[id][-1]) == type(frames[keys[key]]):
                        if self.page_template[id][-1].tag_mergeable():
                            self.page_template[id][-1].merge(frames[keys[key]])
                        continue
                self.page_template[id].append(frames[keys[key]])
        self.template = self.template_order[0]

    def _get_style(self):
        return self.style

    def set_next_template(self):
        self.template = self.template_order[(self.template_order.index(name)+1) % self.template_order]
        self.frame_pos = -1

    def set_template(self, name):
        self.template = name
        self.frame_pos = -1

    def frame_start(self):
        result = ''
        frames = self.page_template[self.template]
        ok = True
        while ok:
            self.frame_pos += 1
            if self.frame_pos>=len(frames):
                self.frame_pos=0
                self.loop=1
                ok = False
                continue
            f = frames[self.frame_pos]
            result+=f.tag_start()
            ok = not f.tag_end()
            if ok:
                result+=f.tag_stop()
        return result

    def frame_stop(self):
        frames = self.page_template[self.template]
        f = frames[self.frame_pos]
        result=f.tag_stop()
        return result

    def start(self):
        return ''
    
    def end(self):
        return "template end\n"

class _rml_doc(object):
    def __init__(self, node, localcontext=None, images=None, path='.', title=None):
        self.localcontext = {} if localcontext is None else localcontext
        self.etree = node
        self.filename = self.etree.get('filename')
        self.result = ''

    def render(self, out):
        #el = self.etree.findall('docinit')
        #if el:
            #self.docinit(el)

        #el = self.etree.findall('stylesheet')
        #self.styles = _rml_styles(el,self.localcontext)

        el = self.etree.findall('template')
        self.result =""
        if len(el):
            pt_obj = _rml_template(self.localcontext, out, el[0], self)
            stories = utils._child_get(self.etree, self, 'story')
            for story in stories:
                if self.result:
                    self.result += '\f'
                f = _flowable(pt_obj,story,self.localcontext)
                self.result += f.render(story)
                del f
        else:
            self.result = "<cannot render w/o template>"
        self.result += '\n'
        out.write( self.result)

def parseNode(rml, localcontext=None,fout=None, images=None, path='.',title=None):
    node = etree.XML(rml)
    r = _rml_doc(node, localcontext, images, path, title=title)
    fp = StringIO.StringIO()
    r.render(fp)
    return fp.getvalue()

def parseString(rml, localcontext=None,fout=None, images=None, path='.',title=None):
    node = etree.XML(rml)
    r = _rml_doc(node, localcontext, images, path, title=title)
    if fout:
        fp = file(fout,'wb')
        r.render(fp)
        fp.close()
        return fout
    else:
        fp = StringIO.StringIO()
        r.render(fp)
        return fp.getvalue()

def trml2pdf_help():
    print 'Usage: rml2txt input.rml >output.html'
    print 'Render the standard input (RML) and output an TXT file'
    sys.exit(0)

if __name__=="__main__":
    if len(sys.argv)>1:
        if sys.argv[1]=='--help':
            trml2pdf_help()
        print parseString(file(sys.argv[1], 'r').read()).encode('iso8859-7')
    else:
        print 'Usage: trml2txt input.rml >output.pdf'
        print 'Try \'trml2txt --help\' for more information.'

# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

