#!/usr/bin/python3
# -*- coding: utf-8 -*-

#  Copyright © 2012-2017  B. Clausius <barcc@gmx.de>
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.



import sys, os
sys.path.insert(0, '.')
import re
from pathlib import Path
from textwrap import fill, wrap

from pybiklib import config

try:
    _
except NameError:
    import builtins
    builtins.__dict__['_'] = lambda s: s

footnote_first_idx = 1

def underline(title, underline_char='='):
    return '{}\n{}'.format(title, underline_char*len(title))

def clean_links(text):
    global footnote_first_idx
    footnote_cur_idx = 0
    footnotes = []
    def add_footnote(match):
        nonlocal footnote_cur_idx
        if match.group(2) != '|':
            return match.group(0)
        elif match.group(1) != '':
            footnote = match.group(1)
            if footnote in footnotes:
                footnote_cur_idx = footnotes.index(footnote) + footnote_first_idx
            else:
                footnote_cur_idx = len(footnotes) + footnote_first_idx
                footnotes.append(footnote)
            return ''
        else:
            return ' [{}]'.format(footnote_cur_idx)
    text = re.sub(r'<(.*?)(\|?)>', add_footnote, text)
    footnotes = ['[{}] <{}>'.format(i+footnote_first_idx, footnote) for i, footnote in enumerate(footnotes)]
    footnote_first_idx += len(footnotes)
    return text, footnotes
    
def wrap_text(text):
    for line in text.splitlines():
        if not line:
            yield ''
        elif len(line) <= 78:
            yield ' '+line if line.startswith(('* ', ' ')) else line
        elif line.startswith(('* ', ' ')):
            if ': ' in line:
                line1, line2 = line.split(': ', 1)
                assert len(line1) <= 78
                yield ' '+line1+':'
                yield from wrap(line2, width=78, initial_indent='   ', subsequent_indent='   ')
            else:
                yield from wrap(line, width=78, initial_indent=' ', subsequent_indent=' ')
        else:
            yield from wrap(line, width=78)
            
def lp_link(text):
    from subprocess import check_output
    fileid = check_output(('bzr', 'file-id', text), universal_newlines=True).strip()
    filename = os.path.basename(text)
    return '{}/head:/{}/{}'.format(config.REPOSITORY_DOWNLOAD, fileid, filename)
    
def xmlmarkup(lines):
    text = ''
    islist = False
    for line in lines:
        if line.startswith('* '):
            line = line[2:]
            assert line.strip() == line
            if islist:
                text += '</li>'
            else:
                text += '\n    <ul>'
            text += '\n      <li>'
            text += line
            islist = True
        elif line.startswith('  '):
            line = line[2:]
            assert line.strip() == line
            assert islist
            text += ' '+line
        else:
            assert line.strip() == line
            if islist:
                text += '</li>'
                text += '\n    </ul>'
            if line:
                text += '\n    <p>'+line+'</p>'
            islist = False
    if islist:
        text += '</li>'
        text += '\n    </ul>'
    text += '\n  '
    return text
    
def parse_NEWS():
    class S:    version, sep1, list1, listN, textN = range(5)
    nstate = S.version
    with open('NEWS', 'rt', encoding='utf-8') as f:
        text = f.read()
    news = []
    # news[i] : release i
    # news[i][0] : version of release i
    # news[i][1] : date of release i
    # news[i][2] : list of changes in release i
    # news[i][2][j] : change j in release i
    # news[i][2][j][0] : text of change j in release i
    # news[i][2][j][1] : sublist of change j in release i
    # news[i][2][j][1][k] : subitem k of change j in release i
    for line in text.splitlines():
        if nstate == S.version:
            assert line == line.strip()
            match = re.match(r'^(\w*) ([0-9.]*) \((\d\d\d\d-\d\d-\d\d|unreleased)\):$', line, flags=re.ASCII)
            name, version, date = match.groups()
            assert name == config.APPNAME
            if len(news) == 0:
                assert version == config.VERSION_SHORT
                assert date == config.RELEASE_DATE
            else:
                assert date != 'unreleased'
            news.append((version, date, []))
            nstate = S.sep1
        elif nstate == S.sep1:
            assert line == '', line
            nstate = S.list1
        elif nstate == S.list1:
            assert len(news[-1][2]) == 0
            if line.startswith('  * '):
                line = line[4:]
                assert line == line.strip()
                news[-1][2].append((line, []))
                nstate = S.listN
            else:
                assert line.startswith('  ')
                line = line[2:]
                assert line == line.strip()
                news[-1][2].append((line, []))
                nstate = S.textN
        elif nstate == S.listN:
            assert len(news[-1][2]) > 0
            if line.startswith('  * '):
                line = line[4:]
                assert line == line.strip()
                news[-1][2].append((line, []))
                nstate = S.listN
            elif line.startswith('    - '):
                line = line[6:]
                assert line == line.strip()
                news[-1][2][-1][1].append(line)
                nstate = S.listN
            elif line.startswith('    '):
                line = line.lstrip()
                assert line == line.strip()
                assert line and line[0] not in list(' *-')
                assert len(news[-1][2][-1][1]) == 0
                news[-1][2][-1] = (news[-1][2][-1][0] + ' ' + line, [])
                nstate = S.listN
            else:
                assert line == '', line
                nstate = S.version
        elif nstate == S.textN:
            assert len(news[-1][2]) == 1
            assert len(news[-1][2][0][1]) == 0
            if line.startswith('  '):
                line = line[2:]
                assert line == line.strip()
                assert line and line[0] not in list(' *-')
                news[-1][2][0] = (news[-1][2][0][0] + ' ' + line, [])
                nstate = S.listN
            elif line.startswith('    - '):
                line = line[6:]
                assert line == line.strip()
                news[-1][2][-1][1].append(line)
                nstate = S.listN
            else:
                assert line == '', line
                nstate = S.version
    assert all(len(n[2]) for n in news)
    return news
    
def xmlrelease(news):
    text = ''
    for version, date, changes in news:
        text += '\n    <release version="{}" date="{}">'.format(version, date)
        text += '\n      <description>'
        one = len(changes) == 1
        if not one:
            text += '\n        <ul>'
        for line, sublist in changes:
            text += '\n          '+('<p>' if one else '<li>')+line
            if sublist:
                text += '\n            <ul>'
                for line in sublist:
                    text += '\n              <li>'+line+'</li>'
                text += '\n            </ul>'
                text += '\n          '
            text += '</p>' if one else '</li>'
        if not one:
            text += '\n        </ul>'
        text += '\n      </description>'
        text += '\n    </release>'
    return text
    
            
class TemplateStr (str):
    __slots__ = ()
    linefmt = None
    delimiters = None
    footnotes = []
    mark = ''
    indent = None
    replacevals = None
    
    @classmethod
    def reset(cls):
        cls.linefmt = None
        cls.delimiters = None
        cls.footnotes.clear()
        cls.mark = ''
        
    def __format__(self, fmt):
        if fmt == 'line':
            text = self
            fmt = self.linefmt
            self.__class__.linefmt = None
        elif self == '#':
            self.__class__.linefmt = fmt
            return ''
        elif self == '##':
            self.__class__.linefmt = 'skip'
            return ''
        elif self == '#delimiters':
            delims = fmt.split()
            if delims:
                if len(delims) != 2:
                    raise IndexError('wrong count of delimiters: '+fmt)
                self.__class__.delimiters = re.escape(delims[0]) + r'(.*?)' + re.escape(delims[1]), r'{\1}'
            else:
                self.__class__.delimiters = None
            return ''
        elif self == '#footnotes':
            if not self.footnotes:
                return ''
            text = '\n' + '\n'.join(self.footnotes) + '\n'
            self.__class__.footnotes.clear()
        elif self == '#mark':
            self.__class__.mark = fmt
            return ''
        elif self == '#indent':
            self.__class__.indent = fmt
            return ''
        elif self == '#replace':
            self.__class__.replacevals = fmt.split(None, 1)
            return ''
        elif self == '#data':
            text, fmt = fmt.split(None, 1)
        elif self == '#news':
            #FIXME: should fail here, if NEWS not in depends
            text = parse_NEWS()
            text = xmlrelease(text)
        else:
            text = getattr(config, self)
            if callable(text):
                text = text()
        if not fmt:
            return text
        for s in fmt.split('.'):
            if s == 'wrap':
                text = '\n'.join(wrap_text(text))
            elif s == 'deb_text':
                text = '\n '.join(l or '.' for l in text.splitlines())
            elif s == 'links':
                text, footnotes = clean_links(text)
                self.__class__.footnotes += footnotes
            elif s == 'join':
                if isinstance(text, (tuple, list)):
                    text = '\n'.join(text)
                else:
                    text = text.replace('\n\n', '  ').replace('\n', ' ')
            elif s == 'indent':
                indent = self.indent or ' '
                text = '\n'.join((l and indent+l) for l in text.split('\n'))
            elif s == 'replace':
                rsrc, rdst = self.replacevals
                text = text.replace(rsrc, rdst)
            elif s == 'skip':
                text = ''
            elif s == 'xmlmarkup':
                text = xmlmarkup(text)
            elif s == 'lp-link':
                text = lp_link(text)
            else:
                raise ValueError('unknown format: ' + fmt)
        return text
        
        
class TemplateKeyDict (dict):
    __slots__ = ()
    def __getitem__(self, key):
        if key and key[0] in ["'", '"']:
            import ast
            return TemplateStr(ast.literal_eval(key))
        return TemplateStr(key)
        
        
def get_template_filename(templatename):
    if templatename == os.path.basename(templatename):
        templatename = os.path.join(config.appdata_dir, 'templates', templatename)
    templatename += '.in'
    return templatename
    
def create_doc(templatename, filename=None, *, skip=None):
    global footnote_first_idx
    footnote_first_idx = 1
    if not filename:
        filename = templatename
    templatename = get_template_filename(templatename)
    with open(templatename, 'rt', encoding='utf-8') as f:
        template = f.readlines()
    text = ''
    prev_linelen = None
    skipstate = 0
    TemplateStr.reset()
    for lineno, line in enumerate(template):
        line = line.rstrip()
        if skip == line:
            skipstate = 1
            continue
        elif skipstate == 3:
            skipstate = 0
        elif skipstate:
            skipstate = 1 if line else skipstate+1
            continue
        if prev_linelen is not None and 3 <= len(line) == line.count(line[0]):
            line = line[0] * prev_linelen
        else:
            if TemplateStr.delimiters is not None:
                line = line.replace('{','{{').replace('}','}}')
                pattern, replace = TemplateStr.delimiters
                line = re.sub(pattern, replace, line)
            try:
                line = line.format_map(TemplateKeyDict())
                line = format(TemplateStr(line), 'line')
            except Exception as e:
                print('{}:{}: {}'.format(templatename, lineno+1, e))
                print('   ', line, end='')
                raise
            prev_linelen = len(line)
        if skip == TemplateStr.mark:
            continue
        text += line + '\n'
    with open(filename, 'wt', encoding='utf-8') as f:
        f.write(text)
    
    
def main():
    skip = None
    args = sys.argv[1:]
    if not args:
        print('usage: {} [--skip=PARAGRAPH] template[=filename]'.format(os.path.basename(sys.argv[0])))
        sys.exit(1)
    for arg in args:
        if arg.startswith('--skip='):
            skip = arg.split('=', 1)[1]
        elif not arg.startswith('--'):
            fileinfo = arg.split('=', 1)
            create_doc(*fileinfo, skip=skip)
        else:
            print('Unknown Option:', arg)
            sys.exit(1)
            
    
if __name__ == '__main__':
    try:
        _
    except NameError:
        __builtins__._ = lambda text: text
    main()
    

