{*******************************************************}
{                                                       }
{       Tocsg.Valid                                     }
{                                                       }
{       Copyright (C) 2025 kku                          }
{                                                       }
{*******************************************************}

unit Tocsg.Valid;

interface

uses
  System.SysUtils, Winapi.Windows;

function IsValidKoreanRegNo(sRegNo: String): Boolean;

implementation

uses
  System.DateUtils, Tocsg.Exception;

const
// 주민번호, 외국인등록번호 검증 가중치
  arrWeights: array[1..12] of Integer = (2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5);

// 주민번호에서 생년월일 검증
function IsValidDateFromSSN(const SSN: string): Boolean;
var
  nYear, nMonth, nDay, nFYear, n: Integer;
  dtTemp: TDateTime;
begin
  Result := false;

  if Length(SSN) < 7 then
    exit;

  try
    nYear := StrToIntDef(Copy(SSN, 1, 2), -1);
    if nYear = -1 then exit;
    nMonth := StrToIntDef(Copy(SSN, 3, 2), -1);
    if nMonth = -1 then exit;
    nDay := StrToIntDef(Copy(SSN, 5, 2), -1);
    if nDay = -1 then exit;
    n := StrToIntDef(SSN[7], -1);
    if n = -1 then exit;

    case n of
      1, 2: nFYear := 1900 + nYear;
      3, 4: nFYear := 2000 + nYear;
      5, 6: nFYear := 1900 + nYear; // 외국인 등록번호 (2000년 이전)
      7, 8: nFYear := 2000 + nYear; // 외국인 등록번호 (2000년 이후)
      else exit;
    end;

    Result := TryEncodeDate(nFYear, nMonth, nDay, dtTemp);
  except
    on E: Exception do
      ETgException.TraceException(E, 'Fail .. IsValidDateFromSSN()');
  end;
end;

function IsValidForeignRegNo(sRegNo: String): Boolean;
var
  i, nSum, nCheck, nLastDigit: Integer;
begin
  Result := false;

  if Length(sRegNo) < 13 then
    exit;

  // 외국인 등록번호는 7번째 자리가 5~8번이어야 함
  if not (sRegNo[7] in ['5', '6', '7', '8']) then
    exit;

  // 가중치 계산
  nSum := 0;
  for i := 1 to 12 do
    nSum := nSum + StrToInt(sRegNo[i]) * arrWeights[i];

  // 검증 숫자 계산
  nCheck := (11 - (nSum mod 11) + 2) mod 10;
  nLastDigit := StrToInt(sRegNo[13]);

  Result := nCheck = nLastDigit;
end;

// 주민번호 검증
// 외국인등록번호 검증 추가 25_0623 10:45:05 kku
// 2017년 이후 새로운 주민번호 방식(예: 지역번호 변경 등)에는 대응하지 않음
function IsValidKoreanRegNo(sRegNo: String): Boolean;
var
  i, nSum, nCheck, nCalc: Integer;
begin
  Result := false;

  sRegNo := StringReplace(sRegNo, '-', '', [rfReplaceAll]);

  // 생년월일 유효성 체크
  if not IsValidDateFromSSN(sRegNo) then
    exit;

  // 13자리 숫자인지 확인
  if Length(sRegNo) < 13 then
    exit;

  // 외국인 등록번호는 7번째 자리가 5~8번임
  if sRegNo[7] in ['5', '6', '7', '8'] then
  begin
    Result := IsValidForeignRegNo(sRegNo);
    exit;
  end;

  // 숫자만으로 이루어졌는지 확인
  for i := 1 to 13 do
    if not CharInSet(sRegNo[i], ['0'..'9']) then
      exit;

  // 가중치 계산
  nSum := 0;
  for i := 1 to 12 do
    nSum := nSum + (Ord(sRegNo[i]) - Ord('0')) * arrWeights[i];

  // 검증 숫자 계산
  nCalc := (11 - (nSum mod 11)) mod 10;
  nCheck := Ord(sRegNo[13]) - Ord('0');

  Result := nCalc = nCheck;
end;

end.
