# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""
=head1 NAME

bunnylog - Python module to parse BunnyCDN log files

=head1 SYNOPSIS

  import bunnylog

  bw = 0

  with open('server.log') as logfile:
    for line in logfile:
      log = bunnylog.parse_line(line)

      bw += log.length

  print(f"Used {bw} bytes of bandwidth")

=head1 DESCRIPTION

This module aims to parse the log files generated by BunnyCDN. You can then
take these and input them to your database or convert them to other log
formats.

=cut

"""

from collections import namedtuple

"""
=head1 MODEL

=head2 LogEntry

This module parses the log entries into I<LogEntry> objects. Each object has
the following values.

=over

=item B<hit> - I<bool>

This value indicates if a request was a cache hit

=item B<status_code> - I<int>

The status code returned by the server

=item B<timestamp> - I<int>

The timestamp of the request

=item B<length> - I<int>

The length of the response in bytes

=item B<pull_server> - I<str>

The ID of the pull server on BunnyCDN

=item B<ip> - I<str>

The IP address of the visitor

=item B<referrer> - I<str>

The referrer page, indicated by the referer header

=item B<url> - I<str>

The URL for the request

=item B<cdn_country> - I<str>

The country code for the CDN node that served this request

=item B<user_agent> - I<str>

The User-Agent header of the request

=item B<content_hash> - I<str>

The hash of the content that was sent to the client

=item B<visitor_country> - I<str>

The country code of the visitor

=back

=cut

"""

LogEntry = namedtuple(
    "LogEntry",
    [
        "hit",
        "status_code",
        "timestamp",
        "length",
        "pull_server",
        "ip",
        "referrer",
        "url",
        "cdn_country",
        "user_agent",
        "content_hash",
        "visitor_country",
    ],
)


"""
=head1 INTERFACE

=head2 parse_line

Takes a log line as a string and returns a parsed LogEntry.

=cut

"""
def parse_line(line: str) -> LogEntry:
    parts = iter(line.strip().split("|"))

    return LogEntry(
        __next_with_default(parts, None) == "HIT",
        int(__next_with_default(parts, "0")),
        int(__next_with_default(parts, "0")),
        int(__next_with_default(parts, "0")),
        __next_with_default(parts, ""),
        __next_with_default(parts, ""),
        __next_with_default(parts, ""),
        __next_with_default(parts, ""),
        __next_with_default(parts, ""),
        __next_with_default(parts, ""),
        __next_with_default(parts, ""),
        __next_with_default(parts, ""),
    )

def __next_with_default(iterator, default=None):
    val = next(iterator, "")
    if val == "":
        return default

    return val
