#
# Copyright (C) 2000 Stephen Davies
# Copyright (C) 2000 Stefan Seefeld
# All rights reserved.
# Licensed to the public under the terms of the GNU LGPL (>= 2),
# see the file COPYING for details.
#

"""
View base class, contains base functionality and common interface for all Views.
"""

from Synopsis.Processor import Parametrized, Parameter
from Synopsis.Formatters import open_file
from Tags import *

import re, os

class Format(Parametrized):
    """Default and base class for formatting a view layout. The Format
    class basically defines the HTML used at the start and end of the view.
    The default creates an XHTML compliant header and footer with a proper
    title, and link to the stylesheet."""

    def init(self, processor, prefix):

        self.prefix = prefix

    def view_header(self, os, title, body, headextra, view):
        """Called to output the view header to the given output stream.
        @param os a file-like object (use os.write())
        @param title the title of this view
        @param body the body tag, which may contain extra parameters such as
        onLoad scripts, and may also be empty eg: for the frames index
        @param headextra extra html to put in the head section, such as
        scripts
        """

        os.write('<?xml version="1.0" encoding="iso-8859-1"?>\n')
        os.write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\n')
        os.write('    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n')
        os.write('<html xmlns="http://www.w3.org/1999/xhtml" lang="en">\n')
        os.write('<!-- ' + view.filename() + ' -->\n')
        os.write('<!-- this view was generated by ' + view.__class__.__name__ + ' -->\n')
        os.write("<head>\n")
        os.write('<meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type"/>\n')
        os.write(element('title','Synopsis - '+ title) + '\n')
        css = self.prefix + 'style.css'
        os.write(element('link', type='text/css', rel='stylesheet', href=css) + '\n')
        os.write(headextra)
        os.write("</head>\n%s\n"%body)

    def view_footer(self, os, body):
        """Called to output the view footer to the given output stream.
        @param os a file-like object (use os.write())
        @param body the close body tag, which may be empty eg: for the frames
        index
        """

        os.write("\n%s\n</html>\n"%body)

class Template(Format):
    """Format subclass that uses a template file to define the HTML header
    and footer for each view."""

    template = Parameter('', 'the html template file')
    copy_files = Parameter([], 'a list of files to be copied into the output dir')
   
    def init(self, processor, prefix):
      
        Format.init(self, processor, prefix)
        self.__re_body = re.compile('<body(?P<params>([ \t\n]+[-a-zA-Z0-9]+=("[^"]*"|\'[^\']*\'|[^ \t\n>]*))*)>', re.I)
        self.__re_closebody = re.compile('</body>', re.I)
        self.__re_closehead = re.compile('</head>', re.I)
        self.__title_tag = '@TITLE@'
        self.__content_tag = '@CONTENT@'
        for file in self.copy_files:
            processor.file_layout.copy_file(file, file)
        self.load_file()

    def load_file(self):
        """Loads and parses the template file"""

        f = open(self.template, 'rt')
        text = f.read(1024*64) # arbitrary max limit of 64kb
        f.close()
        # Find the content tag
        content_index = text.find(self.__content_tag)
        if content_index == -1:
            print "Fatal: content tag '%s' not found in template file!"%self.__content_tag
            raise SystemError, "Content tag not found"
        header = text[:content_index]
        # Find the title (doesn't matter if not found)
        self.__title_index = text.find(self.__title_tag)
        if self.__title_index != -1:
            # Remove the title tag
            header = header[:self.__title_index] + \
                     header[self.__title_index+len(self.__title_tag):]
        # Find the close head tag
        mo = self.__re_closehead.search(header)
        if mo: self.__headextra_index = mo.start()
        else: self.__headextra_index = -1
        # Find the body tag
        mo = self.__re_body.search(header)
        if not mo:
            print "Fatal: body tag not found in template file!"
            print "(if you are sure there is one, this may be a bug in Synopsis)"
            raise SystemError, "Body tag not found"
        if mo.group('params'): self.__body_params = mo.group('params')
        else: self.__body_params = ''
        self.__body_index = mo.start()
        header = header[:mo.start()] + header[mo.end():]
        # Store the header
        self.__header = header
        footer = text[content_index+len(self.__content_tag):]
        # Find the close body tag
        mo = self.__re_closebody.search(footer)
        if not mo:
            print "Fatal: close body tag not found in template file"
            raise SystemError, "Close body tag not found"
        self.__closebody_index = mo.start()
        footer = footer[:mo.start()] + footer[mo.end():]
        self.__footer = footer

    def write(self, os, text):
        """Writes the text to the output stream, replaceing @PREFIX@ with the
        prefix for this file"""

        sections = text.split('@PREFIX@')
        os.write(self.prefix.join(sections))

    def view_header(self, os, title, body, headextra, view):
        """Formats the header using the template file"""

        if not body: return Format.view_header(self, os, title, body, headextra)
        header = self.__header
        index = 0
        if self.__title_index != -1:
            self.write(os, header[:self.__title_index])
            self.write(os, title)
            index = self.__title_index
        if self.__headextra_index != -1:
            self.write(os, header[index:self.__headextra_index])
            self.write(os, headextra)
            index = self.__headextra_index
        self.write(os, header[index:self.__body_index])
        if body:
            if body[-1] == '>':
                self.write(os, body[:-1]+self.__body_params+body[-1])
            else:
                # Hmmmm... Should not happen, perhaps use regex?
                self.write(os, body)
        self.write(os, header[self.__body_index:])

    def view_footer(self, os, body):
        """Formats the footer using the template file"""

        if not body: return Format.view_footer(self, os, body)
        footer = self.__footer
        self.write(os, footer[:self.__closebody_index])
        self.write(os, body)
        self.write(os, footer[self.__closebody_index:])

class View(Parametrized):
    """Base class for Views. The base class provides a common interface, and
    also handles common operations such as opening the file, and delegating
    the view formatting to a strategy class."""

    template = Parameter(Format(), 'the object that provides the html template for the view')
   
    def __init__(self, **kwds):

        super(View, self).__init__(**kwds)
        self.main = False

    def register(self, frame):
        """Registers this View class with its frame."""

        self.frame = frame
        self.directory_layout = self.frame.processor.directory_layout
        self.processor = frame.processor
        self.__os = None

    def filename(self):
        """Return the filename (currently) associated with the view."""

        return ''

    def title(self):
        """Return the title (currently) associated with the view."""

        return ''

    def root(self):
        """Return a pair of (url, label) to link to the entry point of this view."""

        return None, None

    def write_navigation_bar(self):
        """Generate a navigation bar for this view."""

        self.write(self.frame.navigation_bar(self) + '\n')

    def os(self):
        "Returns the output stream opened with start_file"

        return self.__os

    def write(self, str):
        """Writes the given string to the currently opened file"""

        self.__os.write(str)

    def register_filenames(self):
        """Register filenames for each file this View will generate."""

        pass

    def toc(self):
        """Retrieves the TOC for this view. This method assumes that the view
        generates info for the the whole ASG, which could be the Scope,
        the Source (source code) or the XRef (cross reference info).
        The default implementation returns None."""

        pass
       
    def process(self):
        """Process the ASG, creating view-specific html pages."""

        pass

    def open_file(self):
        """Returns a new output stream. This template method is for internal
        use only, but may be overriden in derived classes.
        The default joins output dir and self.filename()"""

        path = os.path.join(self.processor.output, self.filename())
        return open_file(path)

    def close_file(self):
        """Closes the internal output stream. This template method is for
        internal use only, but may be overriden in derived classes."""

        self.__os.close()
        self.__os = None
	
    def start_file(self, body='', headextra=''):
        """Start a new file with given filename, title and body. This method
        opens a file for writing, and writes the html header crap at the top.
        You must specify a title, which is prepended with the project name.
        The body argument is optional, and it is preferred to use stylesheets
        for that sort of stuff. You may want to put an onLoad handler in it
        though in which case that's the place to do it. The opened file is
        stored and can be accessed using the os() method."""

        self.__os = self.open_file()
        prefix = rel(self.filename(), '')
        self.template.init(self.processor, prefix)
        if not body:
            body = '<body class="%s">'%self.__class__.__name__
        self.template.view_header(self.__os, self.title(), body, headextra, self)
    
    def end_file(self, body='</body>'):
        """Close the file using given close body tag. The default is
        just a close body tag, but if you specify '' then nothing will be
        written (useful for a frames view)"""

        self.template.view_footer(self.__os, body)
        self.close_file()

    def reference(self, name, scope, label=None, **keys):
        """Returns a reference to the given name. The name is a scoped name,
        and the optional label is an alternative name to use as the link text.
        The name is looked up in the TOC so the link may not be local. The
        optional keys are appended as attributes to the A tag."""

        if not label: label = escape(str(scope.prune(name)))
        entry = self.processor.toc[name]
        if entry: return href(rel(self.filename(), entry.link), label, **keys)
        return label or ''
