#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Original source: github.com/okfn/bibserver
# Authors:
# markmacgillivray
# Etienne Posthumus (epoz)
# Francois Boulogne <fboulogne at april dot org>

import sys
import logging

logger = logging.getLogger(__name__)

__all__ = ['BibTexParser']


if sys.version_info >= (3, 0):
    from io import StringIO
    ustr = str
else:
    from StringIO import StringIO
    ustr = unicode


class BibTexParser(object):
    """
    A parser for bibtex files.

    By default (i.e. without customizations), each value in entries are considered
    as a string.

    :param fileobj: a filehandler
    :param customization: a function

    Example:

    >>> from bibtexparser.bparser import BibTexParser
    >>> filehandler = open('bibtex', 'r')
    >>> parser = BibTexParser(filehandler)
    >>> record_list = parser.get_entry_list()
    >>> records_dict = parser.get_entry_dict()

    """
    def __init__(self, fileobj, customization=None):
        data = fileobj.read()

        # On some sample data files, the character encoding detection simply hangs
        # We are going to default to utf8, and mandate it.
        self.encoding = 'utf8'

        # Some files have Byte-order marks inserted at the start
        if data[:3] == '\xef\xbb\xbf':
            data = data[3:]
        self.fileobj = StringIO(data)

        # set which bibjson schema this parser parses to
        self.has_metadata = False
        self.persons = []
        # if bibtex file has substition strings, they are stored here,
        # then the values are checked for those substitions in _add_val
        self.replace_dict = {}
        # pre-defined set of key changes
        self.alt_dict = {
            'keyw': 'keyword',
            'keywords': 'keyword',
            'authors': 'author',
            'editors': 'editor',
            'url': 'link',
            'urls': 'link',
            'links': 'link',
            'subjects': 'subject'
        }

        self.records = self._parse_records(customization=customization)
        self.entries_hash = {}

    def get_entry_list(self):
        """Get a list of bibtex entries.

        :retuns: list -- entries
        """
        return self.records

    def get_entry_dict(self):
        """Get a dictionnary of bibtex entries.
        The dict key is the bibtex entry key

        :retuns: dict -- entries
        """
        # If the hash has never been made, make it
        if not self.entries_hash:
            for entry in self.records:
                self.entries_hash[entry['id']] = entry
        return self.entries_hash

    def _parse_records(self, customization=None):
        """Parse the bibtex into a list of records.

        :param customization: a function
        :returns: list -- records
        """
        def _add_parsed_record(record, records):
            """
            Atomic function to parse a record
            and append the result in records
            """
            if record != "":
                logger.debug('The record is not empty. Let\'s parse it.')
                parsed = self._parse_record(record, customization=customization)
                if parsed:
                    logger.debug('Store the result of the parsed record')
                    records.append(parsed)
                else:
                    logger.debug('Nothing returned from the parsed record!')
            else:
                logger.debug('The record is empty')

        records = []
        record = ""
        # read each line, bundle them up until they form an object, then send for parsing
        for linenumber, line in enumerate(self.fileobj):
            logger.debug('Inspect line %s', linenumber)
            if '--BREAK--' in line:
                logger.debug('--BREAK-- encountered')
                break
            else:
                if line.strip().startswith('@'):
                    logger.debug('Line starts with @')
                    _add_parsed_record(record, records)
                    logger.debug('The record is set to empty')
                    record = ""
                if len(line.strip()) > 0:
                    logger.debug('The line is not empty, add it to record')
                    record += line

        # catch any remaining record and send it for parsing
        _add_parsed_record(record, records)
        logger.debug('Return the result')
        return records

    def _parse_record(self, record, customization=None):
        """Parse a record.

        * tidy whitespace and other rubbish
        * parse out the bibtype and citekey
        * find all the key-value pairs it contains

        :param record: a record
        :param customization: a function

        :returns: dict --
        """
        d = {}

        if not record.startswith('@'):
            logger.debug('The record does not start with @. Return empty dict.')
            return {}

        # prepare record
        record = '\n'.join([i.strip() for i in record.split('\n')])
        if '}\n' in record:
            record, rubbish = record.replace('\r\n', '\n').replace('\r', '\n').rsplit('}\n', 1)

        # if a string record, put it in the replace_dict
        if record.lower().startswith('@string'):
            logger.debug('The record startswith @string')