#============================================================================
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#============================================================================
# Copyright (C) 2004, 2005 Mike Wray <mike.wray@hp.com>
# Copyright (C) 2005 XenSource Ltd
#============================================================================

"""General pretty-printer, including support for SXP.

"""
import sys
import types
import StringIO
from xen.xend import sxp

class PrettyItem:

    def __init__(self, width):
        self.width = width

    def insert(self, block):
        block.addtoline(self)

    def get_width(self):
        return self.width

    def output(self, _):
        print '***PrettyItem>output>', self
        pass

    def prettyprint(self, _):
        print '***PrettyItem>prettyprint>', self
        return self.width

class PrettyString(PrettyItem):

    def __init__(self, x):
        PrettyItem.__init__(self, len(x))
        self.value = x

    def output(self, out):
        out.write(self.value)

    def prettyprint(self, line):
        line.output(self)

    def show(self, out):
        print >> out, ("(string (width %d) '%s')" % (self.width, self.value))

class PrettySpace(PrettyItem):

    def output(self, out):
        out.write(' ' * self.width)

    def prettyprint(self, line):
        line.output(self)

    def show(self, out):
        print >> out, ("(space (width %d))" % self.width)
        
class PrettyBreak(PrettyItem):

    def __init__(self, width, indent):
        PrettyItem.__init__(self, width)
        self.indent = indent
        self.space = 0
        self.active = 0

    def output(self, out):
        out.write(' ' * self.width)

    def prettyprint(self, line):
        if line.breaks(self.space):
            self.active = 1
            line.newline(self.indent)
        else:
            line.output(self)

    def show(self, out):
        print >> out, ("(break (width %d) (indent %d) (space %d) (active %d))"
                       % (self.width, self.indent, self.space, self.active))

class PrettyNewline(PrettySpace):

    def insert(self, block):
        block.newline()
        block.addtoline(self)

    def prettyprint(self, line):
        line.newline(0)
        line.output(self)

    def show(self, out):
        print >> out, ("(nl (width %d))" % self.width)

class PrettyLine(PrettyItem):
    def __init__(self):
        PrettyItem.__init__(self, 0)
        self.content = []

    def write(self, x):
        self.content.append(x)

    def end(self):
        width = 0
        lastwidth = 0
        lastbreak = None
        for x in self.content:
            if isinstance(x, PrettyBreak):
                if lastbreak:
                    lastbreak.space = (width - lastwidth)
                lastbreak = x
                lastwidth = width
            width += x.get_width()
        if lastbreak:
            lastbreak.space = (width - lastwidth)
        self.width = width
 
    def prettyprint(self, line):
        for x in self.content:
            x.prettyprint(line)

    def show(self, out):
        print >> out, '(LINE (width %d)' % self.width
        for x in self.content:
            x.show(out)
        print >> out, ')'

class PrettyBlock(PrettyItem):

    def __init__(self, all=0, parent=None):
        PrettyItem.__init__(self, 0)

        self.lines = []
        self.parent = parent
        self.indent = 0
        self.all = all
        self.broken = 0
        self.newline()

    def add(self, item):
        item.insert(self)

    def end(self):
        self.width = 0
        for l in self.lines:
            l.end()
            if self.width < l.width:
                self.width = l.width

    def breaks(self, _):
        return self.all and self.broken

    def newline(self):
        self.lines.append(PrettyLine())

    def addtoline(self, x):
        self.lines[-1].write(x)

    def prettyprint(self, line):
        self.indent = line.used
        line.block = self
        if not line.fits(self.width):
            self.broken = 1
        for l in self.lines:
            l.prettyprint(line)
        line.block = self.parent

    def show(self, out):
        print >> out, ('(BLOCK (width %d) (indent %d) (all %d) (broken %d)' %
                       (self.width, self.indent, self.all, self.broken))
        for l in self.lines:
            l.show(out)
        print >> out, ')'

class Line:

    def __init__(self, out, width):
        self.block = None
        self.out = out
        self.width = width
        self.used = 0
        self.space = self.width

    def newline(self, indent):
        indent += self.block.indent
        self.out.write('\n')
        self.out.write(' ' * indent)
        self.used = indent
        self.space = self.width - self.used

    def fits(self, n):
        return self.space - n >= 0

    def breaks(self, n):
        return self.block.breaks(n) or not self.fits(n)

    def output(self, x):
        n = x.get_width()
        self.space -= n
        self.used += n
        if self.space < 0:
            self.space = 0
        x.output(self.out)

class PrettyPrinter:
    """A prettyprinter based on what I remember of Derek Oppen's
    prettyprint algorithm from TOPLAS way back.
    """

    def __init__(self, width=40):
        self.width = width
        self.block = None
        self.top = None

    def write(self, x):
        self.block.add(PrettyString(x))

    def add(self, item):
        self.block.add(item)

    def addbreak(self, width=1, indent=4):
        self.add(PrettyBreak(width, indent))

    def addspace(self, width=1):
        self.add(PrettySpace(width))

    def addnl(self, indent=0):
        self.add(PrettyNewline(indent))

    def begin(self, all=0):
        block = PrettyBlock(all=all, parent=self.block)
        self.block = block

    def end(self):
        self.block.end()
        if self.block.parent:
            self.block.parent.add(self.block)
        else:
            self.top = self.block
        self.block = self.block.parent

    def prettyprint(self, out=sys.stdout):
        self.top.prettyprint(Line(out, self.width))

class SXPPrettyPrinter(PrettyPrinter):
    """An SXP prettyprinter.
    """
    
    def pstring(self, x):
        io = StringIO.StringIO()
        sxp.show(x, out=io)
        io.seek(0)
        val = io.getvalue()
        io.close()
        return val

    def pprint(self, l):
        if isinstance(l, types.ListType):
            self.begin(all=1)
            self.write('(')
            i = 0
            for x in l:
                if(i): self.addbreak()
                self.pprint(x)
                i += 1
            self.addbreak(width=0, indent=0)
            self.write(')')
            self.end()
        else:
            self.write(self.pstring(l))

def prettyprint(sxpr, out=sys.stdout, width=80):
    """Prettyprint an SXP form.

    sxpr  s-expression
    out   destination
    width maximum output width
    """
    if isinstance(sxpr, types.ListType):
        pp = SXPPrettyPrinter(width=width)
        pp.pprint(sxpr)
        pp.prettyprint(out=out)
    else:
        sxp.show(sxpr, out=out)
    print >> out

def prettyprintstring(sxpr, width=80):
    """Prettyprint an SXP form to a string.

    sxpr  s-expression
    width maximum output width
    """
    io = StringIO.StringIO()
    prettyprint(sxpr, out=io, width=width)
    io.seek(0)
    val = io.getvalue()
    io.close()
    return val

def main():
    pin = sxp.Parser()
    while 1:
        buf = sys.stdin.read(100)
        pin.input(buf)
        if buf == '': break
    l = pin.get_val()
    prettyprint(l, width=80)

if __name__ == "__main__":
    main()
    
