"""Code to handle the output of PEP 0."""
from __future__ import absolute_import
from __future__ import print_function
import datetime
import sys
import unicodedata

from operator import attrgetter

from . import constants
from .pep import PEP, PEPError

# This is a list of reserved PEP numbers.  Reservations are not to be used for
# the normal PEP number allocation process - just give out the next available
# PEP number.  These are for "special" numbers that may be used for semantic,
# humorous, or other such reasons, e.g. 401, 666, 754.
#
# PEP numbers may only be reserved with the approval of a PEP editor.  Fields
# here are the PEP number being reserved and the claimants for the PEP.
# Although the output is sorted when PEP 0 is generated, please keep this list
# sorted as well.
RESERVED = [
    (801, 'Warsaw'),
    ]


indent = u' '

def emit_column_headers(output):
    """Output the column headers for the PEP indices."""
    column_headers = {'status': '.', 'type': '.', 'number': 'PEP',
                        'title': 'PEP Title', 'authors': 'PEP Author(s)'}
    print(constants.table_separator, file=output)
    print(constants.column_format % column_headers, file=output)
    print(constants.table_separator, file=output)


def sort_peps(peps):
    """Sort PEPs into meta, informational, accepted, open, finished,
    and essentially dead."""
    meta = []
    info = []
    provisional = []
    accepted = []
    open_ = []
    finished = []
    historical = []
    deferred = []
    dead = []
    for pep in peps:
        # Order of 'if' statement important.  Key Status values take precedence
        # over Type value, and vice-versa.
        if pep.status == 'Draft':
            open_.append(pep)
        elif pep.status == 'Deferred':
            deferred.append(pep)
        elif pep.type_ == 'Process':
            if pep.status == "Active":
                meta.append(pep)
            elif pep.status in ("Withdrawn", "Rejected"):
                dead.append(pep)
            else:
                historical.append(pep)
        elif pep.status in ('Rejected', 'Withdrawn',
                            'Incomplete', 'Superseded'):
            dead.append(pep)
        elif pep.type_ == 'Informational':
            # Hack until the conflict between the use of "Final"
            # for both API definition PEPs and other (actually
            # obsolete) PEPs is addressed
            if (pep.status == "Active" or
                "Release Schedule" not in pep.title):
                info.append(pep)
            else:
                historical.append(pep)
        elif pep.status == 'Provisional':
            provisional.append(pep)
        elif pep.status in ('Accepted', 'Active'):
            accepted.append(pep)
        elif pep.status == 'Final':
            finished.append(pep)
        else:
            raise PEPError("unsorted (%s/%s)" %
                           (pep.type_, pep.status),
                           pep.filename, pep.number)
    return (meta, info, provisional, accepted, open_,
            finished, historical, deferred, dead)


def verify_email_addresses(peps):
    authors_dict = {}
    for pep in peps:
        for author in pep.authors:
            # If this is the first time we have come across an author, add them.
            if author not in authors_dict:
                authors_dict[author] = [author.email]
            else:
                found_emails = authors_dict[author]
                # If no email exists for the author, use the new value.
                if not found_emails[0]:
                    authors_dict[author] = [author.email]
                # If the new email is an empty string, move on.
                elif not author.email:
                    continue
                # If the email has not been seen, add it to the list.
                elif author.email not in found_emails:
                    authors_dict[author].append(author.email)

    valid_authors_dict = {}
    too_many_emails = []
    for author, emails in authors_dict.items():
        if len(emails) > 1:
            too_many_emails.append((author.first_last, emails))
        else:
            valid_authors_dict[author] = emails[0]
    if too_many_emails:
        err_output = []
        for author, emails in too_many_emails:
            err_output.append("    %s: %r" % (author, emails))
        raise ValueError("some authors have more than one email address "
                         "listed:\n" + '\n'.join(err_output))

    return valid_authors_dict


def sort_authors(authors_dict):
    authors_list = list(authors_dict.keys())
    authors_list.sort(key=attrgetter('sort_by'))
    return authors_list

def normalized_last_first(name):
    return len(unicodedata.normalize('NFC', name.last_first))

def emit_title(text, anchor, output, *, symbol="="):
    print(".. _{anchor}:\n".format(anchor=anchor), file=output)
    print(text, file=output)
    print(symbol*len(text), file=output)
    print(file=output)

def emit_subtitle(text, anchor, output):
    emit_title(text, anchor, output, symbol="-")

def emit_pep_category(output, category, anchor, peps):
    emit_subtitle(category, anchor, output)
    emit_column_headers(output)
    for pep in peps:
        print(pep, file=output)
    print(constants.table_separator, file=output)
    print(file=output)

def write_pep0(peps, output=sys.stdout):
    # PEP metadata
    today = datetime.date.today().strftime("%Y-%m-%d")
    print(constants.header % today, file=output)
    print(file=output)
    # Introduction
    emit_title("Introduction", "intro", output)
    print(constants.intro, file=output)
    print(file=output)
    # PEPs by category
    (meta, info, provisional, accepted, open_,
           finished, historical, deferred, dead) = sort_peps(peps)
    emit_title("Index by Category", "by-category", output)
    emit_pep_category(
        category="Meta-PEPs (PEPs about PEPs or Processes)",
        anchor="by-category-meta",
        peps=meta,
        output=output,
    )
    emit_pep_category(
        category="Other Informational PEPs",
        anchor="by-category-other-info",
        peps=info,
        output=output,
    )
    emit_pep_category(
        category="Provisional PEPs (provisionally accepted; interface may still change)",
        anchor="by-category-provisional",
        peps=provisional,
        output=output,
    )
    emit_pep_category(
        category="Accepted PEPs (accepted; may not be implemented yet)",
        anchor="by-category-accepted",
        peps=accepted,
        output=output,
    )
    emit_pep_category(
        category="Open PEPs (under consideration)",
        anchor="by-category-open",
        peps=open_,
        output=output,
    )
    emit_pep_category(
        category="Finished PEPs (done, with a stable interface)",
        anchor="by-category-finished",
        peps=finished,
        output=output,
    )
    emit_pep_category(
        category="Historical Meta-PEPs and Informational PEPs",
        anchor="by-category-historical",
        peps=historical,
        output=output,
    )
    emit_pep_category(
        category="Deferred PEPs (postponed pending further research or updates)",
        anchor="by-category-deferred",
        peps=deferred,
        output=output,
    )
    emit_pep_category(
        category="Abandoned, Withdrawn, and Rejected PEPs",
        anchor="by-category-abandoned",
        peps=dead,
        output=output,
    )
    print(file=output)
    # PEPs by number
    emit_title("Numerical Index", "by-pep-number", output)
    emit_column_headers(output)
    prev_pep = 0
    for pep in peps:
        if pep.number - prev_pep > 1:
            print(file=output)
        print(constants.text_type(pep), file=output)
        prev_pep = pep.number
    print(constants.table_separator, file=output)
    print(file=output)
    # Reserved PEP numbers
    emit_title('Reserved PEP Numbers', "reserved", output)
    emit_column_headers(output)
    for number, claimants in sorted(RESERVED):
        print(constants.column_format % {
            'type': '.',
            'status': '.',
            'number': number,
            'title': 'RESERVED',
            'authors': claimants,
            }, file=output)
    print(constants.table_separator, file=output)
    print(file=output)
    # PEP types key
    emit_title("PEP Types Key", "type-key", output)
    for type_ in sorted(PEP.type_values):
        print(u"    %s - %s PEP" % (type_[0], type_), file=output)
        print(file=output)
    print(file=output)
    # PEP status key
    emit_title("PEP Status Key", "status-key", output)
    for status in sorted(PEP.status_values):
        # Draft PEPs have no status displayed, Active shares a key with Accepted
        if status in ("Active", "Draft"):
            continue
        if status == "Accepted":
            msg = "    A - Accepted (Standards Track only) or Active proposal"
        else:
            msg = "    {status[0]} - {status} proposal".format(status=status)
        print(msg, file=output)
        print(file=output)

    print(file=output)
    # PEP owners
    emit_title("Authors/Owners", "authors", output)
    authors_dict = verify_email_addresses(peps)
    max_name = max(authors_dict.keys(), key=normalized_last_first)
    max_name_len = len(max_name.last_first)
    author_table_separator = "="*max_name_len + "  " + "="*len("email address")
    print(author_table_separator, file=output)
    _author_header_fmt = "{name:{max_name_len}}  Email Address"
    print(_author_header_fmt.format(name="Name", max_name_len=max_name_len), file=output)
    print(author_table_separator, file=output)
    sorted_authors = sort_authors(authors_dict)
    _author_fmt = "{author.last_first:{max_name_len}}  {author_email}"
    for author in sorted_authors:
        # Use the email from authors_dict instead of the one from 'author' as
        # the author instance may have an empty email.
        _entry = _author_fmt.format(
            author=author,
            author_email=authors_dict[author],
            max_name_len=max_name_len,
        )
        print(_entry, file=output)
    print(author_table_separator, file=output)
    print(file=output)
    print(file=output)
    # References for introduction footnotes
    emit_title("References", "references", output)
    print(constants.references, file=output)
    print(constants.footer, file=output)
