# bis.py - functions for handling Belgian BIS numbers
# coding: utf-8
#
# Copyright (C) 2023 Jeff Horemans
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA

"""BIS (Belgian BIS number).


The BIS number (BIS-nummer, Numéro BIS) is a unique identification number
for individuals who are not registered in the National Register, but who
still have a relationship with the Belgian government.
This includes frontier workers, persons who own property in Belgium,
persons with Belgian social security rights but who do not reside in Belgium, etc.

The number is issued by the Belgian Crossroads Bank for Social Security (Banque
Carrefour de la sécurité sociale, Kruispuntbank voor Sociale Zekerheid) and is
constructed much in the same way as the Belgian National Number, i.e. consisting of
11 digits, encoding the person's date of birth and gender, a checksum, etc.
Other than with the national number though, the month of birth of the BIS number
is increased by 20 or 40, depending on whether the sex of the person was known
at the time or not.


More information:

* https://sma-help.bosa.belgium.be/en/faq/where-can-i-find-my-bis-number#7326
* https://www.socialsecurity.be/site_nl/employer/applics/belgianidpro/index.htm
* https://nl.wikipedia.org/wiki/Rijksregisternummer
* https://fr.wikipedia.org/wiki/Numéro_de_registre_national

>>> compact('98.47.28-997.65')
'98472899765'
>>> validate('98 47 28 997 65')
'98472899765'
>>> validate('01 49 07 001 85')
'01490700185'
>>> validate('12345678901')
Traceback (most recent call last):
    ...
InvalidChecksum: ...
>>> format('98472899765')
'98.47.28-997.65'
>>> get_birth_date('98.47.28-997.65')
datetime.date(1998, 7, 28)
>>> get_birth_year('98.47.28-997.65')
1998
>>> get_birth_month('98.47.28-997.65')
7
>>> get_gender('98.47.28-997.65')
'M'
"""

from __future__ import annotations

import datetime

from stdnum.be import nn
from stdnum.exceptions import *
from stdnum.util import isdigits


def compact(number: str) -> str:
    """Convert the number to the minimal representation. This strips the number
    of any valid separators and removes surrounding whitespace."""
    return nn.compact(number)


def validate(number: str) -> str:
    """Check if the number is a valid BIS Number."""
    number = compact(number)
    if not isdigits(number) or int(number) <= 0:
        raise InvalidFormat()
    if len(number) != 11:
        raise InvalidLength()
    nn._get_birth_date_parts(number)
    if not 20 <= int(number[2:4]) <= 32 and not 40 <= int(number[2:4]) <= 52:
        raise InvalidComponent('Month must be in 20..32 or 40..52 range')
    return number


def is_valid(number: str) -> bool:
    """Check if the number is a valid BIS Number."""
    try:
        return bool(validate(number))
    except ValidationError:
        return False


def format(number: str) -> str:
    """Reformat the number to the standard presentation format."""
    return nn.format(number)


def get_birth_year(number: str) -> int | None:
    """Return the year of the birth date."""
    return nn.get_birth_year(number)


def get_birth_month(number: str) -> int | None:
    """Return the month of the birth date."""
    return nn.get_birth_month(number)


def get_birth_date(number: str) -> datetime.date | None:
    """Return the date of birth."""
    return nn.get_birth_date(number)


def get_gender(number: str) -> str | None:
    """Get the person's gender ('M' or 'F'), which for BIS
    numbers is only known if the month was incremented with 40."""
    number = compact(number)
    if int(number[2:4]) >= 40:
        return nn.get_gender(number)
    return None
