from __future__ import print_function

import sys, re
import os.path

class Doc2HTML(object):
    
    def __init__(self, filename, outdir, split_level=0):
        """The `split_level` specifies, which level number gets its own page."""
        with open(filename, 'r') as f:
            self.docstr = f.readlines()

        # position for iterations
        self._pos = -1
        self._lines = len(self.docstr)
        self.output_directory = outdir
        self.outfile = None

        # level at which the documentation is split into sub pages: 0 is
        # for a single-page file, 1 splits each part into an own page, 2
        # every section.
        self._split_level = split_level

        # Basename of the single-file output file. This must not be
        # 'gnuplot', since this interferes with the multi-page output.
        self._single_filename = 'gnuplot-single'

        self._part_number = 0

        self.toc_tmpl = '<div class="sidebar-nav-container"><div class="sidebar-nav well"><dl class="toc">{}</dl></div></div>'
        self.code_tmpl = '<pre class="codelisting">{}</pre>'
        self.backquotes_tmpl = '<span class="backquotes">{}</span>'
        self.header_tmpl = '<h{}>{}</h{}>'
        self.label_tmpl = '<a name="{}"></a>'
        self.link_tmpl = '<a href="{}">{}</a>'
        self.par_tmpl = '<p>{}</p>'
        self.list_tmpl = '<ul>{}</ul>'

        # Holds a lookup table to assign references encountered in
        # backquotes to the actual pages and respective labels
        # therein. The keys of the dictionary are the reference names as
        # found in the text, and the values are lists which contain the
        # page name as first and anchor name as second entry,
        # e.g. dict('set xrange': ('commands.html', '#set-xrange')).
        self._labels = dict()
        self.__create_link_lookup_table()
        self.__create_table_of_contents()

        with open(os.path.join(self.output_directory, 'style.css'), 'w') as f:
            print(CSS_STYLE, file=f)

        
    def __create_link_lookup_table(self):
        """Create a lookup table to assign references encountered in backquotes to
        the actual pages and labels.
        """
        if (self._split_level == 0):
            gpdoc_pages = (self._single_filename, ''.join(self.docstr))
        else:
            pattern = r'^[0-{}] (.*?)$'.format(self._split_level)
            gpdoc_pages = re.split(pattern, ''.join(self.docstr), flags=re.MULTILINE)[1:]

        for i in range(0,len(gpdoc_pages),2):
            labels = re.findall(r'^\?(.*?)$', gpdoc_pages[i+1], flags=re.MULTILINE)
            
            page_name = self.get_page_name(gpdoc_pages[i])
                
            for l in labels:
                self._labels[l.strip()] = (page_name, self.get_relative_link(l))

                
    def __create_table_of_contents(self):
        """Generate the table of contents."""
        level = 1
        new_entry = False

        if (self._split_level == 0):
            self._current_page = self.get_page_name(self._single_filename)
        else:
            self._current_page = ''

        self.nav_list = []
            
        # CSS names for the levels
        self._part_number = 0
        level_names = {1: 'part', 2: 'section', 3: 'subsection', 4: 'subsubsection', 5: 'paragraph', 6: 'subparagraph'}
        toc = ''
        
        self._pos = 0

        while (self._pos < self._lines):
            line = self.get_line()
            self._pos += 1
            if line.startswith('?') and new_entry:
                # the first label after the new page or section was found
                label = line[1:].strip()
                # close any preceeding levels
                for i in range(0, level - new_level):
                    toc += "</dl></dd>\n"
                
                if (new_level > level):
                    toc += '<dd><dl class="{}">'.format(level_names[new_level])

                link = self.get_link(label)
                if (new_level <= self._split_level):
                    link = re.split('#', link)[0]
                    self.nav_list.append((new_entry.capitalize(), link))
                    
                toc += '<dt><a href="{}">{}</a></dt>'.format(link, new_entry.capitalize())
                new_entry = False
                level = new_level
                continue
        
            if re.match(r'\d ', line):
                # found an new toc entry
                new_level = int(line[:2])
                new_entry = line[2:].strip()
                    
                if (new_level == 1):
                    # found a new part
                    self._part_number += 1
                    new_entry = self.get_part_prefix() + new_entry

        self.toc = self.toc_tmpl.format(toc)
        

    def get_page_name(self, s):
        """Return the name of a HTML page based on a string."""
        return re.sub('[/_ \(\)]', '-', s.lower().strip()) + '.html'

    
    def get_link(self, s):
        """Build the link to an anchor, including the page name if required.

        Returns an empty string if the label wasn't found."""
        s = s.lower().strip()
        # the labels for plotting styles don't use the 'with' part.
        if s.startswith('with'):
            s = s[5:]

        if (not s in self._labels):
            return ''

        if (self._labels[s][0] == self._current_page):
            return self._labels[s][1]
        
        return ''.join(self._labels[s])

    
    def get_relative_link(self, s):
        return '#' + self.get_label(s)

        
    def get_label(self, s):
        # s contains only characters and white spaces.
        return s.strip().replace(' ', '-')

    
    def get_line(self):
        """Return the content of the current line."""
        if (self._pos < self._lines):
            return self.docstr[self._pos]
        else:
            return ''

    
    def peek_line(self, offset=1):
        """Return the content of the line at a position relative to the current."""
        return self.docstr[self._pos + offset]

    
    def get_part_prefix(self):
        """Add a prefix for the top level sections, the 'parts'."""
        return 'Part {}: '.format(self._part_number)

    
    def escape_html(self, s):
        return s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')

    
    """Following are all formatting methods, which may be accessed only from self.format_doc()."""
    
    def format_section(self):
        level = int(self.get_line()[:2])
        title = self.get_line()[2:].strip().capitalize()

        self.format_top_nav(title, level)
                
        # iterate over all following labels to include them inside the heading.
        labels = ''
        while ((self._pos + 1 < self._lines) and
                self.peek_line().startswith('?')):
            self._pos += 1
            labels += self.label_tmpl.format(self.get_label(self.get_line()[1:]))

        # Add a prefix 
        if (level == 1):
            title = self.get_part_prefix() + labels + title
        else:
            title = labels + title

        print(self.header_tmpl.format(level, title, level), file=self.outfile)


    def format_top_nav(self, title, level):
        if (level > self._split_level):
            return None

        for i, v in enumerate(self.nav_list):
            if (v[0] == title):
                break

        top_nav = '<div class="top-nav-row">'
        if (i > 0):
            v = self.nav_list[i-1]
            top_nav += '<div class="prev-nav">{}</div>'.format(self.link_tmpl.format(v[1], 'Prev: '+v[0]))
            
        if (i < len(self.nav_list)-1):
            v = self.nav_list[i+1]
            top_nav += '<div class="next-nav">{}</div>'.format(self.link_tmpl.format(v[1], 'Next: '+v[0]))
        top_nav += '</div>'
        print(top_nav, file=self.outfile)
        
    
    def format_label(self):
        print(self.label_tmpl.format(self.get_label(self.get_line()[1:])), file=self.outfile)

    
    def format_table(self):
        """Extract the HTML table code."""
        table_str = ''
        while (not self.peek_line().startswith('@')):
            self._pos += 1
            if self.get_line().startswith('^'):
                table_str += self.get_line()[1:]

        print(table_str, file=self.outfile)

    
    def format_codelisting(self):
        code_str = ''
        spaces = len(self.get_line()) - len(self.get_line().lstrip())

        while (self.get_line().startswith('   ')):
            code_str += self.get_line()[spaces:]
            self._pos += 1

        self._pos -= 1
        
        print(self.code_tmpl.format(self.escape_html(code_str)), file=self.outfile)

    def format_list(self):
        s = ''
        while (not self.peek_line().startswith('#end')):
            self._pos += 1
            s += '<li><pre class="inline">{}</pre></li>'.format(self.escape_html(self.get_line()[1:].lstrip()))
            
        print(self.list_tmpl.format(s), file=self.outfile)

        
    def format_raw_html(self):
        print(self.get_line()[1:], end='', file=self.outfile)

        
    def format_backquotes(self, matchobj):
        s = matchobj.group(1)
        link = self.get_link(s)
        code = self.backquotes_tmpl.format(s)
        if link:
            return self.link_tmpl.format(link, code)
        return code

    
    def format_paragraph(self):
        par = []
        while re.match(' [^ ]', self.get_line()):
            par.append(self.escape_html(self.get_line().strip()))
            self._pos += 1

        if par:
            self._pos -= 1
            par = ' '.join(par)
            par = re.sub(r'`([^`]*)`', self.format_backquotes, par)
            print(self.par_tmpl.format(par), file=self.outfile)

    def format_image(self):
        img_src = self.get_line()[1:].strip()
        s = '<div class="figure"><img src="{}.png"></div>'.format(img_src)
        print(s, file=self.outfile)

    def open_and_init_file(self, filename):
        self.outfile = open(os.path.join(self.output_directory, filename), 'w')
        print(HTML_HEADER, file=self.outfile)
        print(self.toc, file=self.outfile)
        print('<div class="doc-container">', file=self.outfile)

    def close_and_finish_file(self):
        if self.outfile:
            print('</div>', file=self.outfile)
            print(HTML_FOOTER, file=self.outfile)
            self.outfile.close()
            self.outfile = None
            

    def format_doc(self):
        self._part_number = 0
        page_pattern = r'[0-{}] (.*?)$'.format(self._split_level)
        
        if (self._split_level == 0):
            self._current_page = self.get_page_name(self._single_filename)
            self.open_and_init_file(self._current_page)    

        self._pos = 0
        
        while (self._pos < self._lines):
            line = self.get_line()
            # skip the commented lines before the first section.
            if (line.startswith('C')):
                self._pos += 1
                continue

            if re.match(page_pattern, line):
                self._current_page = self.get_page_name(line[2:].strip())
                self.close_and_finish_file()
                self.open_and_init_file(self._current_page)

            # now do the actual parsing
            if (re.match(r'\d+ ', line)):
                if (int(line[:2]) == 1):
                    self._part_number += 1
                self.format_section()
                
            elif (line.startswith('?')):
                self.format_label()
                
            elif (line.startswith('@start table')):
                if (not self.peek_line(-1).startswith('^<!-- INCLUDE_NEXT_TABLE -->')):
                    self.format_table()

            elif line.startswith('#start'):
                self.format_list()

            elif line.startswith('^'):
                self.format_raw_html()
                
            elif line.startswith('F'):
                self.format_image()
            
            elif line.startswith('   '):
                self.format_codelisting()
                
            elif line.startswith(' '):
                self.format_paragraph()

            # currently we ignore all '#' (not the '#start' one), '%' and '='

            self._pos += 1


          
HTML_HEADER = r"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Gnuplot documentation</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
"""

HTML_FOOTER = r"""</body></html>"""

CSS_STYLE = r"""* {
    box-sizing: border-box;
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
}
body, h1, h2, h3, h4, h5, h6 {
    font-family: sans-serif;
    font-weight: 500;
    line-height=1.1;
}
a {
    color: #477A26;
    text-decoration: none;
}

a:hover, a:focus {
    text-decoration: underline;
}
    

h1 {
    font-size: 3em;
}
h2 {
    font-size: 2.5em;;
}
h3 {
    font-size: 2em;
}
h4, h5, h6 {
    font-size: 1.5em;
}
.backquotes {
    font-weight: bold;
}
.figure {
  width: 460px;
  padding: 4px;
  border: 1px solid #ddd;
  -webkit-border-radius: 4px;
  -moz-border-radius: 4px;
  border-radius: 4px;
  -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075);
  -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075);
  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075);
  /* default centered */
  display: block;
  margin: 10px auto 20px;
  text-align: center;
}
pre {
    border: 1px solid #CCC;
    border-radius: 4px;
    padding: 0.5em;
    background: none repeat scroll 0% 0% #F0F0F0;
}

.doc-container {
    float: left;
    width: 75%;
    padding-left: 15px;
    padding-right: 15px;
}

.sidebar-nav-container {
    float: left;
    width: 25%;
    padding-left: 15px;
    padding-right: 15px;
}
.sidebar-nav {
    font-size: 0.8125em;
}
.sidebar-nav .toc dd {
    margin-left: 1em;
}

.sidebar-nav dl .section {
    margin-bottom: 2em;
}
.sidebar-nav dl .toc dt {
    font-size: 1.2em;
}
.sidebar-nav dl {
    margin-bottom: 1em;
} 
.sidebar-nav dt {
    line-height: 1.5;
}

.prev-nav {
    float: left !important;
}
.next-nav {
    float: right !important;
}
.toc dt {
    font-weight: bold;
}
.toc dd a {
    font-weight: normal;
}

pre .inline {
    display: inline;
}

.well{
    min-height:20px;
    padding:9px;
    margin-bottom:20px;
    background-color:#f5f5f5;
    border:1px solid #e3e3e3;
    border-radius:3px
}
"""
        

if (len(sys.argv) > 1):
    infile = sys.argv[1]

    if (len(sys.argv) == 3) :
        output_directory = sys.argv[2]
    else:
        output_directory = '.'
else:
    raise IOException('You must give an input file to convert.')

# create the single-file documentation
gpdoc = Doc2HTML(infile, output_directory)
gpdoc.format_doc()

gpdoc = Doc2HTML(infile, output_directory, 2)
gpdoc.format_doc()
