"""DBF header definition.

TODO:
  - handle encoding of the character fields
    (encoding information stored in the DBF header)

"""
"""History (most recent first):
16-sep-2010 [als]   fromStream: fix century of the last update field
11-feb-2007 [als]   added .ignoreErrors
10-feb-2007 [als]   added __getitem__: return field definitions
                    by field name or field number (zero-based)
04-jul-2006 [als]   added export declaration
15-dec-2005 [yc]    created
"""

__version__ = "$Revision: 1.6 $"[11:-2]
__date__ = "$Date: 2010/09/16 05:06:39 $"[7:-2]

__all__ = ["DbfHeader"]

import datetime
import io
import struct
import sys

from . import fields
from .utils import getDate


class DbfHeader:
    """Dbf header definition.

    For more information about dbf header format visit
    `http://www.clicketyclick.dk/databases/xbase/format/dbf.html#DBF_STRUCT`

    Examples:
        Create an empty dbf header and add some field definitions:
            dbfh = DbfHeader()
            dbfh.addField(("name", "C", 10))
            dbfh.addField(("date", "D"))
            dbfh.addField(DbfNumericFieldDef("price", 5, 2))
        Create a dbf header with field definitions:
            dbfh = DbfHeader([
                ("name", "C", 10),
                ("date", "D"),
                DbfNumericFieldDef("price", 5, 2),
            ])

    """

    __slots__ = ("signature", "fields", "lastUpdate", "recordLength",
                 "recordCount", "headerLength", "changed", "_ignore_errors")

    # instance construction and initialization methods

    def __init__(self, fields=None, headerLength=0, recordLength=0,
                 recordCount=0, signature=0x03, lastUpdate=None, ignoreErrors=False):
        """Initialize instance.

        Arguments:
            fields:
                a list of field definitions;
            recordLength:
                size of the records;
            headerLength:
                size of the header;
            recordCount:
                number of records stored in DBF;
            signature:
                version number (aka signature). using 0x03 as a default meaning
                "File without DBT". for more information about this field visit
                ``http://www.clicketyclick.dk/databases/xbase/format/dbf.html#DBF_NOTE_1_TARGET``
            lastUpdate:
                date of the DBF's update. this could be a string ('yymmdd' or
                'yyyymmdd'), timestamp (int or float), datetime/date value,
                a sequence (assuming (yyyy, mm, dd, ...)) or an object having
                callable ``ticks`` field.
            ignoreErrors:
                error processing mode for DBF fields (boolean)

        """
        self.signature = signature
        if fields is None:
            self.fields = []
        else:
            self.fields = list(fields)
        self.lastUpdate = getDate(lastUpdate)
        self.recordLength = recordLength
        self.headerLength = headerLength
        self.recordCount = recordCount
        self.ignoreErrors = ignoreErrors
        # XXX: I'm not sure this is safe to
        # initialize `self.changed` in this way
        self.changed = bool(self.fields)

    # @classmethod
    def fromString(cls, string):
        """Return header instance from the string object."""
        return cls.fromStream(io.StringIO(str(string)))
    fromString = classmethod(fromString)

    # @classmethod
    def fromStream(cls, stream):
        """Return header object from the stream."""
        stream.seek(0)
        first_32 = stream.read(32)
        if type(first_32) != bytes:
            _data = bytes(first_32, sys.getfilesystemencoding())
        _data = first_32
        (_cnt, _hdrLen, _recLen) = struct.unpack("<I2H", _data[4:12])
        # reserved = _data[12:32]
        _year = _data[1]
        if _year < 80:
            # dBase II started at 1980.  It is quite unlikely
            # that actual last update date is before that year.
            _year += 2000
        else:
            _year += 1900
        # create header object
        _obj = cls(None, _hdrLen, _recLen, _cnt, _data[0],
                   (_year, _data[2], _data[3]))
        # append field definitions
        # position 0 is for the deletion flag
        _pos = 1
        _data = stream.read(1)
        while _data != b'\r':
            _data += stream.read(31)
            _fld = fields.lookupFor(_data[11]).fromString(_data, _pos)
            _obj._addField(_fld)
            _pos = _fld.end
            _data = stream.read(1)
        return _obj