/*
  This file is part of Anastasis
  Copyright (C) 2020, 2021, 2026 Anastasis SARL

  Anastasis is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
  Foundation; either version 3, or (at your option) any later version.

  Anastasis 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 General Public License for more details.

  You should have received a copy of the GNU General Public License along with
  Anastasis; see the file COPYING.GPL.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file reducer/validation_CZ_BN.c
 * @brief validation of Czech birth numbers
 * @author Christian Grothoff
 */
#include <string.h>
#include <stdbool.h>
#include <stdio.h>


/**
 * Check the YYMMDD part of a birth number.
 *
 * The month carries the sex and, since 2004, an overflow marker: women have
 * 50 added, and an exhausted daily serial adds another 20.
 *
 * @param d the first six characters of the birth number
 * @param allow_overflow true if the +20/+70 month variants are acceptable
 * @param[out] yy set to the two-digit year
 * @return true if the date is well-formed
 */
static bool
check_date (const char *d,
            bool allow_overflow,
            unsigned int *yy)
{
  unsigned int mm;
  unsigned int dd;

  for (unsigned int i = 0; i<6; i++)
    if ( ('0' > d[i]) || ('9' < d[i]) )
      return false;
  *yy = (d[0] - '0') * 10 + (d[1] - '0');
  mm = (d[2] - '0') * 10 + (d[3] - '0');
  dd = (d[4] - '0') * 10 + (d[5] - '0');
  if ( (mm >= 71) && (mm <= 82) )
  {
    if (! allow_overflow)
      return false;
    mm -= 70;
  }
  else if ( (mm >= 51) && (mm <= 62) )
  {
    mm -= 50;
  }
  else if ( (mm >= 21) && (mm <= 32) )
  {
    if (! allow_overflow)
      return false;
    mm -= 20;
  }
  if ( (mm < 1) || (mm > 12) )
    return false;
  if ( (dd < 1) || (dd > 31) )
    return false;
  return true;
}


/**
 * Function to validate a Czech birth number (rodné číslo).
 *
 * The modern form is YYMMDD/XXXC, where the ten digits must be divisible by
 * 11; as a documented exception, a first-nine-digits remainder of 10 is
 * written with a check digit of 0.  Numbers issued before 1954 have only nine
 * digits (YYMMDD/XXX) and carry no check digit at all, so nothing beyond the
 * date can be verified for them.  Those are accepted only when the year reads
 * as 1900-1953; a nine-digit number claiming a later year is a truncated
 * modern one.  (People born before 1900 also hold nine-digit numbers, which
 * this therefore rejects.)
 *
 * @param b_number birth number to validate (input)
 * @return true if b_number is valid
 */
bool
CZ_BN_check (const char *b_number);

/* declaration to fix compiler warning */
bool
CZ_BN_check (const char *b_number)
{
  size_t len = strlen (b_number);
  unsigned int yy;

  if ( (10 != len) && (11 != len) )
    return false;
  if ('/' != b_number[6])
    return false;
  for (size_t i = 7; i<len; i++)
    if ( ('0' > b_number[i]) || ('9' < b_number[i]) )
      return false;
  if (! check_date (b_number,
                    11 == len,
                    &yy))
    return false;
  if (10 == len)
  {
    /* pre-1954 form: no check digit exists */
    return (yy < 54);
  }
  {
    unsigned long long n = 0;
    unsigned long long base;
    unsigned int check;

    for (unsigned int i = 0; i<6; i++)
      n = n * 10 + (b_number[i] - '0');
    for (unsigned int i = 7; i<11; i++)
      n = n * 10 + (b_number[i] - '0');
    base = n / 10;
    check = (unsigned int) (n % 10);
    if (10 == base % 11)
      return (0 == check);
    return (0 == n % 11);
  }
}
