{
    This file is part of the Free Pascal run time library.
    Copyright (c) 2016 by the Free Pascal development team.

    Android locale support.

    See the file COPYING.FPC, included in this distribution,
    for details about the copyright.

    This program 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.
 **********************************************************************}

unit clocale;

{$mode objfpc}

interface

uses
  cwstring, SysUtils;

procedure GetAndroidFormatSettings(var ASettings: TFormatSettings; ALocale: utf8string = '');

implementation

type
  UErrorCode = SizeInt;
  int32_t = longint;
  uint32_t = longword;
  UBool = LongBool;

  UDateFormat = pointer;
  UDateFormatStyle = longint;
  UDate = double;
  UNumberFormat = pointer;
  UNumberFormatStyle = longint;
  UNumberFormatSymbol = longint;

const
  UDAT_NONE   = -1;
  UDAT_FULL   = 0;
  UDAT_LONG   = 1;
  UDAT_MEDIUM = 2;
  UDAT_SHORT  = 3;

  UNUM_DECIMAL_SEPARATOR_SYMBOL = 0;
  UNUM_GROUPING_SEPARATOR_SYMBOL = 1;
  UNUM_CURRENCY_SYMBOL = 8;

var
  FunctionsInited: boolean;

  udat_open: function (timeStyle: UDateFormatStyle; dateStyle: UDateFormatStyle; locale: PAnsiChar; tzID: PUnicodeChar; tzIDLength: int32_t;
                       pattern: PUnicodeChar; patternLength: int32_t; var status: UErrorCode): UDateFormat; cdecl;
  udat_close: procedure (format: UDateFormat); cdecl;
  udat_toPattern: function (format: UDateFormat; localized: UBool; result: PUnicodeChar; resultLength: int32_t; var status: UErrorCode): int32_t; cdecl;
  udat_applyPattern: procedure (format: UDateFormat; localized: UBool; pattern: PUnicodeChar; patternLength: int32_t); cdecl;
  udat_format: function (format: UDateFormat; dateToFormat: UDate; result: PUnicodeChar; resultLength: int32_t; position: pointer; var status: UErrorCode): int32_t; cdecl;

  unum_open: function (style: UNumberFormatStyle; pattern: PUnicodeChar; patternLength: int32_t; locale: PAnsiChar; parseErr: pointer;
                       var status: UErrorCode): UNumberFormat; cdecl;
  unum_close: procedure (fmt: UNumberFormat); cdecl;
  unum_getSymbol: function (fmt: UNumberFormat; symbol: UNumberFormatSymbol; result: PUnicodeChar; resultLength: int32_t; var status: UErrorCode): int32_t; cdecl;

  GetIcuProc: function (const Name: AnsiString; var ProcPtr; libId: longint): boolean; external name 'ANDROID_GET_ICU_PROC';

procedure InitIcuFunctions;
begin
  if FunctionsInited then exit;
  if not Assigned(GetIcuProc) then exit;
  if not GetIcuProc('udat_open', udat_open, 1) then exit;
  if not GetIcuProc('udat_close', udat_close, 1) then exit;
  if not GetIcuProc('udat_toPattern', udat_toPattern, 1) then exit;
  if not GetIcuProc('udat_applyPattern', udat_applyPattern, 1) then exit;
  if not GetIcuProc('udat_format', udat_format, 1) then exit;
  if not GetIcuProc('unum_open', unum_open, 1) then exit;
  if not GetIcuProc('unum_close', unum_close, 1) then exit;
  if not GetIcuProc('unum_getSymbol', unum_getSymbol, 1) then exit;

  FunctionsInited:=True;
end;

{$ifdef android}

function GetCurrentLocaleStr: utf8string;
var
  s: utf8string;
begin
  Result:=GetSystemProperty('persist.sys.locale');
  if Result <> '' then
    exit;
  Result:=GetSystemProperty('persist.sys.language');
  if Result = '' then
    exit;
  s:=GetSystemProperty('persist.sys.country');
  if s = '' then
    exit;
  Result:=Result + '_' + s;
end;

{$endif android}

function StrOfChar(c: AnsiChar; Count: integer): utf8string;
begin
  SetLength(Result, Count);
  FillChar(PAnsiChar(Result)^, Count, c);
end;

function ConvertFormatStr(const fmt: utf8string): utf8string;
var
  cnt: integer;
  c, q: AnsiChar;
  p: PAnsiChar;
  s: utf8string;
begin
  Result:='';
  q:=#0;
  cnt:=1;
  p:=PAnsiChar(fmt);
  while p^<>#0 do
    begin
      s:='';
      c:=p^;
      if c in ['''', '"'] then
        begin
          if q=#0 then
            q:=c
          else
            if c=q then
              begin
                q:=#0;
                cnt:=1;
              end;
          s:=c;
        end
      else if q <> #0 then
        s:=c
      else
        begin
          if (p+1)^=c then
            Inc(cnt)
          else
            begin
              case c of
                'y', 'Y':
                  begin
                    c:='y';
                    if cnt > 2 then
                      cnt:=4
                    else
                      cnt:=2;
                  end;
                'M', 'L':
                  begin
                    c:='m';
                    if cnt > 4 then
                      cnt:=3;
                  end;
                'd':
                  if cnt > 2 then
                    cnt:=2;
                'E', 'e', 'c':
                  begin
                    c:='d';
                    if (cnt < 3) or (cnt > 4) then
                      cnt:=3;
                  end;
                'a':
                  begin
                    cnt:=0;
                    s:='ampm';
                  end;
                'h', 'H', 'k', 'K':
                  begin
                    c:='h';
                    if cnt > 2 then
                      cnt:=2;
                  end;
                'm':
                  begin
                    c:='n';
                    if cnt>2 then
                      cnt:=2;
                  end;
                's':
                  if cnt>2 then
                    cnt:=2;
                'S':
                  begin
                    c:='z';
                    cnt:=1;
                  end;
                'G','u','Q','q','w','W','D','F','g','A','z','Z','v':
                  cnt:=0;
              end;
              if cnt>0 then
                s:=StrOfChar(c, cnt);
              cnt:=1;
            end;
        end;
      Inc(p);
      if s<>'' then
        Result:=Result+s;
    end;
end;

procedure GetAndroidFormatSettings(var ASettings: TFormatSettings; ALocale: utf8string);
const
  SGMT = 'GMT';

var
  locale: ansistring;

  function _GetFormat(dateStyle: UDateFormatStyle; timeStyle: UDateFormatStyle; const DefFormat: utf8string): utf8string;
  var
    fmt: UDateFormat;
    err: UErrorCode;
    res: unicodestring;
  begin
    Result:='';
    err:=0;
    fmt:=udat_open(timeStyle, dateStyle, PAnsiChar(locale), SGMT, Length(SGMT), nil, 0, err);
    if fmt <> nil then
      begin
        SetLength(res, 200);
        SetLength(res, udat_toPattern(fmt, False, PUnicodeChar(res), Length(res), err));
        udat_close(fmt);
        Result:=ConvertFormatStr(utf8string(res));
      end;
    if Result = '' then
      Result:=DefFormat;
  end;

  function _DateToStr(fmt: UDateFormat; const AFormat: unicodestring; AYear: integer; AMonth, ADay, AHour: byte): utf8string;
  var
    d: double;
    err: UErrorCode;
    res: unicodestring;
  begin
    d:=EncodeDate(AYear, AMonth, ADay) + EncodeTime(AHour, 0, 0, 0) - UnixDateDelta;
    d:=MSecsPerDay*d;
    udat_applyPattern(fmt, False, PUnicodeChar(AFormat), Length(AFormat));
    err:=0;
    SetLength(res, 200);
    SetLength(res, udat_format(fmt, d, PUnicodeChar(res), Length(res), nil, err));
    Result:=utf8string(res);
  end;

  function _GetSeparator(dateStyle: UDateFormatStyle; timeStyle: UDateFormatStyle; DefSep: char): char;
  var
    fmt: UDateFormat;
    err: UErrorCode;
    s: utf8string;
    p: PAnsiChar;
    res: unicodestring;
  begin
    Result:=DefSep;
    err:=0;
    fmt:=udat_open(timeStyle, dateStyle, PAnsiChar(locale), SGMT, Length(SGMT), nil, 0, err);
    if fmt <> nil then
      begin
        SetLength(res, 200);
        SetLength(res, udat_toPattern(fmt, False, PUnicodeChar(res), Length(res), err));
        s:=_DateToStr(fmt, res, 2000, 1, 1, 0);
        udat_close(fmt);
        s:=Trim(s);
        p:=PAnsiChar(s);
        while p^<>#0 do
          if (p^>' ') and (p^<'A') and not (p^ in ['0'..'9']) then
            begin
              Result:=p^;
              break;
            end
          else
            Inc(p);
      end;
  end;

var
  fmt: UDateFormat;
  nfmt: UNumberFormat;
  err: UErrorCode;
  i: integer;
  res: unicodestring;
begin
  if not FunctionsInited then
    exit;
  locale:=ALocale;
{$ifdef android}
  if locale = '' then
    locale:=GetCurrentLocaleStr;
{$endif android}
  if locale = '' then
    locale:='en_US';
  err:=0;
  with ASettings do
    begin
      nfmt:=unum_open(2, nil, 0, PAnsiChar(locale), nil, err);
      if nfmt <> nil then
        begin
          SetLength(res, 200);
          SetLength(res, unum_getSymbol(nfmt, UNUM_DECIMAL_SEPARATOR_SYMBOL, PUnicodeChar(res), Length(res), err));
          if res <> '' then
            DecimalSeparator:=ansichar(res[1]);
          SetLength(res, 200);
          SetLength(res, unum_getSymbol(nfmt, UNUM_GROUPING_SEPARATOR_SYMBOL, PUnicodeChar(res), Length(res), err));
          if res <> '' then
            if Ord(res[1]) < 128 then
              ThousandSeparator:=ansichar(res[1])
            else
              ThousandSeparator:=' ';
          SetLength(res, 200);
          SetLength(res, unum_getSymbol(nfmt, 8, PUnicodeChar(res), Length(res), err));
          CurrencyString:=utf8string(res);
          unum_close(nfmt);
        end;

      DateSeparator:=_GetSeparator(UDAT_SHORT, UDAT_NONE, DateSeparator);
      TimeSeparator:=_GetSeparator(UDAT_NONE, UDAT_SHORT, TimeSeparator);

      LongDateFormat:=_GetFormat(UDAT_LONG, UDAT_NONE, LongDateFormat);
      ShortDateFormat:=_GetFormat(UDAT_SHORT, UDAT_NONE, ShortDateFormat);
      LongTimeFormat:=_GetFormat(UDAT_NONE, UDAT_MEDIUM, LongTimeFormat);
      ShortTimeFormat:=_GetFormat(UDAT_NONE, UDAT_SHORT, ShortTimeFormat);

      fmt:=udat_open(UDAT_NONE, UDAT_NONE, PAnsiChar(locale), SGMT, Length(SGMT), nil, 0, err);
      if fmt <> nil then
        begin
          for i:=1 to 12 do
            begin
              LongMonthNames[i]:=_DateToStr(fmt, 'LLLL', 2006, i, 1, 0);
              ShortMonthNames[i]:=_DateToStr(fmt, 'LLL', 2006, i, 1, 0);
            end;
          for i:=1 to 7 do
            begin
              LongDayNames[i]:=_DateToStr(fmt, 'cccc', 2006, 1, i, 0);
              ShortDayNames[i]:=_DateToStr(fmt, 'ccc', 2006, 1, i, 0);
            end;
          TimeAMString:=_DateToStr(fmt, 'a', 2006, 1, 1, 1);
          TimePMString:=_DateToStr(fmt, 'a', 2006, 1, 1, 13);
          udat_close(fmt);
        end;
    end;
end;

initialization
  InitIcuFunctions;
  GetAndroidFormatSettings(DefaultFormatSettings);

end.

