#!/usr/bin/env python3
#
# aria2 - The high speed download utility
#
# Copyright (C) 2013 Tatsuhiro Tsujikawa
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the
# OpenSSL library under certain conditions as described in each
# individual source file, and distribute linked combinations
# including the two.
# You must obey the GNU General Public License in all respects
# for all of the code used other than OpenSSL.  If you modify
# file(s) with this exception, you may extend this exception to your
# version of the file(s), but you are not obligated to do so.  If you
# do not wish to do so, delete this exception statement from your
# version.  If you delete this exception statement from all source
# files in the program, then also delete it here.
#
# Generates API reference from C++ source code.
from __future__ import print_function
import re, sys, argparse

class FunctionDoc:
    def __init__(self, name, content, domain):
        self.name = name
        self.content = content
        self.domain = domain

    def write(self, out):
        print('''.. {}:: {}'''.format(self.domain, self.name))
        print()
        for line in self.content:
            print('    {}'.format(line))

class TypedefDoc:
    def __init__(self, name, content):
        self.name = name
        self.content = content

    def write(self, out):
        print('''.. type:: {}'''.format(self.name))
        print()
        for line in self.content:
            print('    {}'.format(line))

class StructDoc:
    def __init__(self, name, content, domain, members, member_domain):
        self.name = name
        self.content = content
        self.domain = domain
        self.members = members
        self.member_domain = member_domain

    def write(self, out):
        if self.name:
            print('''.. {}:: {}'''.format(self.domain, self.name))
            print()
            for line in self.content:
                print('    {}'.format(line))
            print()
            for name, content in self.members:
                name = name.strip()
                # For function (e.g., int foo())
                m = re.match(r'(.+)\s+([^ ]+\(.*)', name)
                if not m:
                    # For variable (e.g., bool a)
                    m = re.match(r'(.+)\s+([^ ]+)', name)
                if m:
                    print('''    .. {}:: {} {}::{}'''.format(
                        'function' if name.endswith(')') else self.member_domain,
                        m.group(1),
                        self.name,
                        m.group(2)))
                else:
                    if name.endswith(')'):
                        # For function, without return type, like
                        # constructor
                        print('''    .. {}:: {}::{}'''.format(
                            'function' if name.endswith(')') else self.member_domain,
                            self.name, name))
                    else:
                        # enum
                        print('''    .. {}:: {}'''.format(
                            'function' if name.endswith(')') else self.member_domain,
                            name))
                print()
                for line in content:
                    print('''        {}'''.format(line))
            print()

class MacroDoc:
    def __init__(self, name, content):
        self.name = name
        self.content = content

    def write(self, out):
        print('''.. macro:: {}'''.format(self.name))
        print()
        for line in self.content:
            print('    {}'.format(line))

def make_api_ref(infiles):
    macros = []
    enums = []
    types = []
    functions = []
    for infile in infiles:
        while True:
            line = infile.readline()
            if not line:
                break
            elif line == '/**\n':
                line = infile.readline()
                doctype = line.split()[1]
                if doctype == '@function':
                    functions.append(process_function('function', infile))
                if doctype == '@functypedef':
                    types.append(process_function('type', infile))
                elif doctype == '@typedef':
                    types.append(process_typedef(infile))
                elif doctype in ['@class', '@struct', '@union']:
                    types.append(process_struct(infile))
                elif doctype == '@enum':
                    enums.append(process_enum(infile))
                elif doctype == '@macro':
                    macros.append(process_macro(infile))
    alldocs = [('Macros', macros),
               ('Enums', enums),
               ('Types (classes, structs, unions and typedefs)', types),
               ('Functions', functions)]
    for title, docs in alldocs:
        if not docs:
            continue
        print(title)
        print('-'*len(title))
        for doc in docs:
            doc.write(sys.stdout)
            print()
        print()

def process_macro(infile):
    content = read_content(infile)
    line = infile.readline()
    macro_name = line.split()[1]
    return MacroDoc(macro_name, content)

def process_enum(infile):
    members = []
    enum_name = None
    content = read_content(infile)
    while True:
        line = infile.readline()
        if not line:
            break
        elif re.match(r'\s*/\*\*\n', line):
            member_content = read_content(infile)
            line = infile.readline()
            items = line.split()
            member_name = items[0].rstrip(',')
            if len(items) >= 3:
                member_content.insert(0, '(``{}``) '\
                                          .format(items[2].rstrip(',')))
            members.append((member_name, member_content))
        elif line.startswith('}'):
            if not enum_name:
                enum_name = line.rstrip().split()[1]
            enum_name = re.sub(r';$', '', enum_name)
            break
        elif not enum_name:
            m = re.match(r'^\s*enum\s+([\S]+)\s*{\s*', line)
            if m:
                enum_name = m.group(1)
    return StructDoc(enum_name, content, 'type', members, 'c:macro')

def process_struct(infile):
    members = []
    domain = 'type'
    struct_name = None
    content = read_content(infile)
    while True:
        line = infile.readline()
        if not line:
            break
        elif re.match(r'\s*/\*\*\n', line):
            member_content = read_content(infile)
            line = infile.readline()
            member_name = line.rstrip().rstrip(';')
            member_name = re.sub(r'\)\s*=\s*0', ')', member_name)
            member_name = re.sub(r' virtual ', '', member_name)
            members.append((member_name, member_content))
        elif line.startswith('}') or\
                (line.startswith('typedef ') and line.endswith(';\n')):
            if not struct_name:
                if line.startswith('}'):
                    index = 1
                else:
                    index = 3
                struct_name = line.rstrip().split()[index]
            struct_name = re.sub(r';$', '', struct_name)
            break
        elif not struct_name:
            m = re.match(r'^\s*(struct|class)\s+([\S]+)\s*(?:{|;)', line)
            if m:
                domain = m.group(1)
                if domain == 'struct':
                    domain = 'type'
                struct_name = m.group(2)
                if line.endswith(';\n'):
                    break
    return StructDoc(struct_name, content, domain, members, 'member')

def process_function(domain, infile):
    content = read_content(infile)
    func_proto = []
    while True:
        line = infile.readline()
        if not line:
            break
        elif line == '\n':
            break
        else:
            func_proto.append(line)
    func_proto = ''.join(func_proto)
    func_proto = re.sub(r';\n$', '', func_proto)
    func_proto = re.sub(r'\s+', ' ', func_proto)
    func_proto = re.sub(r'typedef ', '', func_proto)
    return FunctionDoc(func_proto, content, domain)

def process_typedef(infile):
    content = read_content(infile)
    lines = []
    while True:
        line = infile.readline()
        if not line:
            break
        elif line == '\n':
            break
        else:
            lines.append(line)
    typedef = ''.join(lines)
    typedef = re.sub(r';\n$', '', typedef)
    typedef = re.sub(r'\s+', ' ', typedef)
    typedef = re.sub(r'typedef ', '', typedef)
    return TypedefDoc(typedef.split()[-1], content)

def read_content(infile):
    content = []
    while True:
        line = infile.readline()
        if not line:
            break
        if re.match(r'\s*\*/\n', line):
            break
        else:
            content.append(transform_content(line.rstrip()))
    return content

def arg_repl(matchobj):
    return '*{}*'.format(matchobj.group(1).replace('*', '\\*'))

def transform_content(content):
    content = re.sub(r'^\s+\* ?', '', content)
    content = re.sub(r'\|([^\s|]+)\|', arg_repl, content)
    content = re.sub(r':enum:', ':macro:', content)
    return content

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Generate API reference")
    parser.add_argument('--header', type=argparse.FileType('rb', 0),
                        help='header inserted at the top of the page')
    parser.add_argument('files', nargs='+', type=argparse.FileType('rb', 0),
                        help='source file')
    args = parser.parse_args()
    if args.header:
        print(args.header.read())
    for infile in args.files:
        make_api_ref(args.files)
