unit general;

interface

uses
  Windows, Messages, SysUtils, IniFiles,
  m_api,
  ImportT,
  ImportTU;

var
  cp: cardinal;

var
  AppPath: array [0 .. MAX_PATH] of Char;
  TxtPatterns: array of RTxtPattern;
  Protocols: array of TDestProto;
  ProtoCount: integer;
  CheckForDuplicates: boolean;
  ShowDuplicates: boolean;

const
  BIN_PROCEDURE_COUNT = 6; // ���������� ������������ �������� ��������� �������

const
{$EXTERNALSYM PBM_SETRANGE}
  PBM_SETRANGE = WM_USER + 1;
{$EXTERNALSYM PBM_SETPOS}
  PBM_SETPOS = WM_USER + 2;

const
  IMPORT_TXT_MODULE = 'ImportTXT';
  IMPORT_TXT_SERVICE = IMPORT_TXT_MODULE + '/Import';
  IMPORT_WIZ_SERVICE = IMPORT_TXT_MODULE + '/Wizard';

  // keys
  IMPORT_TXT_AS = 'AutoStart';
  IMPORT_TXT_LP = 'LastPattern';

type
  TOnAccountListChange = procedure();

var
  OnAccountListChange: TOnAccountListChange;

procedure ReadPatterns;
procedure ExtractFilePath(fName: PAnsiChar);
function GetContactProto(hContact: THandle): AnsiString;
function GetContactByUID(const proto: AnsiString; const id: AnsiString): THandle;
function GetContactByNick(const proto: AnsiString; const Nick: WideString): THandle;
procedure EnumProtocols;
function GetContactID(hContact: THandle; proto: AnsiString = ''; Contact: boolean = false): WideString;
function GetContactNick(hContact: THandle; proto: AnsiString = ''; Contact: boolean = false): WideString;
function DBReadByte(hContact: THandle; szModule: PAnsiChar; szSetting: PAnsiChar; default: byte = 0): byte;
function DBWriteByte(hContact: THandle; szModule: PAnsiChar; szSetting: PAnsiChar; val: byte): integer;
procedure SetLastPattern(lp: byte);
function GetLastPattern: byte;
function TimeStampToWStr(ts: DWORD): WideString;
function StrToTimeStamp(STime: PAnsiChar; len: integer): DWORD;

function RLWord(adr: integer): word;
function RLInteger(adr: integer): integer;

implementation

procedure ExtractFilePath(fName: PAnsiChar);
var
  p: PAnsiChar;
begin
  p := fName;
  if p <> nil then
  begin
    while p^ <> #0 do
      inc(p);
    while p^ <> '\' do
      dec(p);
    inc(p);
    p^ := #0;
  end;
end;

function ReadPattern(const FileName: String): boolean;
var
  TI: TIniFile;
  tempstr: String;
  pattern:pRTxtPattern;
begin
  TI := TIniFile.Create(FileName);
  try
    if not TI.SectionExists('General') then
      exit;

    pattern:=@TxtPatterns[High(TxtPatterns)];
    // if "General" exists
    if TI.ValueExists('General', 'Name') then
      pattern^.Name := TI.ReadString('General', 'Name', '')
    else
      exit;
    if TI.ValueExists('General', 'Type') then
      pattern^.IType := TI.ReadInteger('General', 'Type', 1)
    else
      exit;
    case pattern^.IType of
      1:
        begin
          if TI.ValueExists('General', 'Charset') then
          begin
            tempstr := TI.ReadString('General', 'Charset', 'UTF8');
            if tempstr = 'ANSI' then
              pattern^.Charset := inANSI
            else if tempstr = 'UTF8' then
              pattern^.Charset := inUTF8
            else if tempstr = 'UCS2' then
              pattern^.Charset := inUCS2
            else
              exit;
          end
          else
            exit;
          if pattern^.Charset = inANSI then
          begin
            pattern^.Codepage := TI.ReadInteger('General', 'Codepage', 0);
            if not IsValidCodePage(pattern^.Codepage) then
              pattern^.Codepage := 0;
          end;
          pattern^.UseHeader := TI.ReadInteger('General', 'UseHeader', 0);
          pattern^.UsePreMsg := TI.ReadBool('General', 'UsePreMsg', false);
          // Read message section
          if TI.SectionExists('Message') then
          begin
            if TI.ValueExists('Message', 'Pattern') then
              pattern^.Msg.Pattern := TI.ReadString('Message', 'Pattern', '')
            else
              exit;
            pattern^.Msg.Incoming := TI.ReadString('Message', 'In', '');
            pattern^.Msg.Outgoing := TI.ReadString('Message', 'Out', '');
            if TI.ValueExists('Message', 'Direction') then
              pattern^.Msg.Direction := TI.ReadInteger('Message', 'Direction', 0)
            else
              exit;
            if TI.ValueExists('Message', 'Day') then
              pattern^.Msg.Day := TI.ReadInteger('Message', 'Day', 0)
            else
              exit;
            if TI.ValueExists('Message', 'Month') then
              pattern^.Msg.Month := TI.ReadInteger('Message', 'Month', 0)
            else
              exit;
            if TI.ValueExists('Message', 'Year') then
              pattern^.Msg.Year := TI.ReadInteger('Message', 'Year', 0)
            else
              exit;
            if TI.ValueExists('Message', 'Hours') then
              pattern^.Msg.Hours := TI.ReadInteger('Message', 'Hours', 0)
            else
              exit;
            if TI.ValueExists('Message', 'Minutes') then
              pattern^.Msg.Minutes := TI.ReadInteger('Message', 'Minutes', 0)
            else
              exit;
            pattern^.Msg.Seconds := TI.ReadInteger('Message', 'Seconds', 0)
          end
          else
            exit;
          // if need read header section
          if (pattern^.UseHeader > 0) then
            if TI.SectionExists('Header') then
            begin
              if TI.ValueExists('Header', 'Pattern') then
                pattern^.Header.Pattern := TI.ReadString('Header', 'Pattern', '')
              else
                exit;
              if (not TI.ValueExists('Header', 'In')) and
                 ((pattern^.UseHeader and 1) = 1) then
                exit;
              pattern^.Header.Incoming := TI.ReadInteger('Header', 'In', 0);
              pattern^.Header.Outgoing := TI.ReadInteger('Header', 'Out', 0);
              pattern^.Header.InNick := TI.ReadInteger('Header', 'InNick', 0);
              pattern^.Header.OutNick := TI.ReadInteger('Header', 'OutNick', 0);
              pattern^.Header.InUID := TI.ReadInteger('Header', 'InUID', 0);
              pattern^.Header.OutUID := TI.ReadInteger('Header', 'OutUID', 0);
              if ((pattern^.UseHeader and 2) = 2) then
                if (pattern^.Header.InNick = 0) and
                   (pattern^.Header.InUID = 0) then
                  exit;
            end
            else
              exit;
          // if nead read PreMessage section
          if pattern^.UsePreMsg then
            if TI.SectionExists('PreMessage') then
            begin
              pattern^.PreMsg.PreRN := TI.ReadInteger('PreMessage', 'PreRN', -1);
              pattern^.PreMsg.AfterRN := TI.ReadInteger('PreMessage', 'AfterRN', -1);
              pattern^.PreMsg.PreSP := TI.ReadInteger('PreMessage', 'PreSP', 0);
              pattern^.PreMsg.AfterSP := TI.ReadInteger('PreMessage', 'AfterSP', 0);
            end
            else
              exit;
        end; // 1
      2:
        begin
          pattern^.BinProc := TI.ReadInteger('General', 'BinProcedure', 0);
          if (pattern^.BinProc > BIN_PROCEDURE_COUNT) then
            exit;
        end;
    end; // case
    pattern^.DefExtension := TI.ReadString('General', 'DefaultExtension', 'txt');
    pattern^.UseFileName := TI.ReadBool('General', 'UseFileName', false);
    // if nead read FileName section
    if pattern^.UseFileName then
      if TI.SectionExists('FileName') then
      begin
        if TI.ValueExists('FileName', 'Pattern') then
          pattern^.fName.Pattern := TI.ReadString('FileName', 'Pattern', '')
        else
          exit;
        pattern^.fName.InNick := TI.ReadInteger('FileName', 'InNick', 0);
        pattern^.fName.InUID := TI.ReadInteger('FileName', 'InUID', 0);
        if (pattern^.fName.InNick = 0) and (pattern^.fName.InUID = 0) then
          exit;
        pattern^.fName.OutNick := TI.ReadInteger('FileName', 'OutNick', 0);
        pattern^.fName.OutUID := TI.ReadInteger('FileName', 'OutUID', 0);
      end
      else
        exit;
  finally
    TI.Free;
    result := true;
  end;
  result := false;
end;

procedure ReadPatterns;
var
  SR: TSearchRec;
  FileAttrs: integer;
  i: integer;
begin
  FileAttrs := faAnyFile;
  i := 0;
  if FindFirst(AppPath + '\importtxt\*.ini', FileAttrs, SR) = 0 then
  begin
    repeat
      SetLength(TxtPatterns, i+1);
      if not ReadPattern(AppPath + '\importtxt\' + SR.Name) then
        inc(i);
    until FindNext(SR) <> 0;
    FindClose(SR);
  end;
  // cut unneded
  SetLength(TxtPatterns, i);
end;

function GetContactByUID(const proto: AnsiString; const id: AnsiString): THandle;
var
  Contact: THandle;
  otherproto: AnsiString;
  ci: TCONTACTINFO;
  idnum: integer;
  tempwstr: PWideChar;
  ws: WideString;
begin
  if not TryStrToInt(id, idnum) then
    idnum := 0;
  tempwstr := UTF8ToWide(PAnsiChar(id), tempwstr);
  ws := tempwstr;
  FreeMem(tempwstr);
  Contact := db_find_first();
  while (Contact <> 0) do
  begin
    otherproto := Proto_GetProtoName(Contact);
    if otherproto = proto then
    begin
      ci.cbSize := SizeOf(ci);
      ci.dwFlag := CNF_UNIQUEID or CNF_UNICODE;
      ci.hContact := Contact;
      ci.szProto := PAnsiChar(otherproto);
      if CallService(MS_CONTACT_GETCONTACTINFO, 0, lparam(@ci)) = 0 then
      begin
        case (ci._type) of
          CNFT_BYTE:   if ci.retval.bVal = idnum then break;
          CNFT_WORD:   if ci.retval.wVal = idnum then break;
          CNFT_DWORD:  if ci.retval.dVal = DWORD(idnum) then break;
          CNFT_ASCIIZ: if ws = ci.retval.szVal.w then break;
        end; // case
      end; // if
    end; // if
    Contact := db_find_next(Contact);
  end; // while
  if Contact=0 then
    result := INVALID_HANDLE_VALUE
  else
    result := Contact;
end;

function GetContactByNick(const proto: AnsiString; const Nick: WideString): THandle;
var
  Contact: THandle;
  otherproto: AnsiString;
  ci: TCONTACTINFO;
begin
  result := INVALID_HANDLE_VALUE;
  Contact := db_find_first();
  while (Contact <> 0) do
  begin
    otherproto := Proto_GetProtoName(Contact);
    if otherproto = proto then
    begin
      ci.cbSize := SizeOf(ci);
      ci.dwFlag := CNF_NICK;
      ci.dwFlag := ci.dwFlag or CNF_UNICODE;
      ci.hContact := Contact;
      ci.szProto := PAnsiChar(otherproto);
      if CallService(MS_CONTACT_GETCONTACTINFO, 0, lparam(@ci)) = 0 then
      begin
        if Nick = ci.retval.szVal.w then
        begin
          result := Contact;
          break;
        end;
      end; // if
    end; // if
    Contact := db_find_next(Contact);
  end; // while
end;

procedure EnumProtocols;
var
  i, iProtoCount: integer;
  ppAccounts: ^PPROTOACCOUNT;
  temps: WideString;
begin
  ProtoCount := 0;
  SetLength(Protocols, 30);
  Proto_EnumAccounts(iProtoCount, ppAccounts);
  for i := 1 to iProtoCount do
  begin
    temps := GetContactID(0, ppAccounts^^.szModuleName, false);
    if temps <> '' then
    begin
      Protocols[ProtoCount].ProtoName := ppAccounts^^.szModuleName;
      Protocols[ProtoCount].ProtoUID := temps;
      Protocols[ProtoCount].ProtoNick := GetContactNick(0, ppAccounts^^.szModuleName, false);
      inc(ProtoCount);
    end;
    inc(ppAccounts);
  end;
  SetLength(Protocols, ProtoCount);
end;

function GetContactProto(hContact: THandle): AnsiString;
begin
  result := Proto_GetProtoName(hContact);
end;

function DBFreeVariant(dbv: PDBVARIANT): integer;
begin
  result := db_free(dbv);
end;

function GetContactID(hContact: THandle; proto: AnsiString = '';
  Contact: boolean = false): WideString;
var
  uid: PAnsiChar;
  dbv: TDBVARIANT;
  tempstr: PWideChar;
begin
  result := '';
  if not((hContact = 0) and Contact) then
  begin
    if proto = '' then
      proto := GetContactProto(hContact);
    uid := PAnsiChar(CallProtoService(PAnsiChar(proto), PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0));
    if (uid <> pAnsiChar(CALLSERVICE_NOTFOUND)) and (uid <> nil) then
    begin
      if db_get(hContact, PAnsiChar(proto), uid, @dbv) = 0 then
      begin
        case dbv._type of
          DBVT_BYTE:
            result := intToStr(dbv.bVal);
          DBVT_WORD:
            result := intToStr(dbv.wVal);
          DBVT_DWORD:
            result := intToStr(dbv.dVal);
          DBVT_ASCIIZ:
            begin
              tempstr := ANSIToWide(dbv.szVal.a, tempstr, cp);
              result := tempstr;
              FreeMem(tempstr);
            end;
          DBVT_UTF8:
            begin
              tempstr := UTF8ToWide(dbv.szVal.a, tempstr);
              result := tempstr;
              FreeMem(tempstr);
            end;
          DBVT_WCHAR:
            result := dbv.szVal.w;
        end;
        // free variant
        DBFreeVariant(@dbv);
      end;
    end;
  end;
end;

function GetContactNick(hContact: THandle; proto: AnsiString = '';
  Contact: boolean = false): WideString;
var
  dbv: TDBVARIANT;
  tempstr: PWideChar;
begin
  result := '';
  if not((hContact = 0) and Contact) then
  begin
    if proto = '' then
      proto := GetContactProto(hContact);
    if db_get(hContact, PAnsiChar(proto), 'Nick', @dbv) = 0 then
    begin
      case dbv._type of
        DBVT_BYTE:
          result := intToStr(dbv.bVal);
        DBVT_WORD:
          result := intToStr(dbv.wVal);
        DBVT_DWORD:
          result := intToStr(dbv.dVal);
        DBVT_ASCIIZ:
          begin
            tempstr := ANSIToWide(dbv.szVal.a, tempstr, cp);
            result := tempstr;
            FreeMem(tempstr);
          end;
        DBVT_UTF8:
          begin
            tempstr := UTF8ToWide(dbv.szVal.a, tempstr);
            result := tempstr;
            FreeMem(tempstr);
          end;
        DBVT_WCHAR:
          result := dbv.szVal.w;
      end;
      // free variant
      DBFreeVariant(@dbv);
    end;
  end;
end;

function DBReadByte(hContact: THandle; szModule: PAnsiChar;
  szSetting: PAnsiChar; default: byte = 0): byte;
var
  dbv: TDBVARIANT;
begin
  If db_get(hContact, szModule, szSetting, @dbv) <> 0 then
    result := default
  else
    result := dbv.bVal;
end;

function DBWriteByte(hContact: THandle; szModule: PAnsiChar; szSetting: PAnsiChar; val: byte): integer;
begin
  result := db_set_b(hContact, szModule, szSetting, val);
end;

procedure SetLastPattern(lp: byte);
begin
  DBWriteByte(0, IMPORT_TXT_MODULE, IMPORT_TXT_LP, lp);
end;

function GetLastPattern: byte;
begin
  result := DBReadByte(0, IMPORT_TXT_MODULE, IMPORT_TXT_LP, 0);
  if result >= Length(TxtPatterns) then
    result := 0;
end;

function TimeStampToWStr(ts: DWORD): WideString;
var
  s: WideString;
begin
  SetLength(s, 20);
  TimeZone_ToStringW(ts, 'd s', PWideChar(s), 20);
  result := s;
end;

function StrToTimeStamp(STime: PAnsiChar; len: integer): DWORD;
var
  hour, min, sec, Day, Month, Year: integer;
begin
  sec := 0;
  Day := (ord(STime[0]) - $30) * 10 + (ord(STime[1]) - $30);
  Month := (ord(STime[3]) - $30) * 10 + (ord(STime[4]) - $30);
  Year := (ord(STime[6]) - $30) * 1000 + (ord(STime[7]) - $30) * 100 +
    (ord(STime[8]) - $30) * 10 + (ord(STime[9]) - $30);
  hour := (ord(STime[11]) - $30) * 10 + (ord(STime[12]) - $30);
  min := (ord(STime[14]) - $30) * 10 + (ord(STime[15]) - $30);
  if len > 15 then
    sec := (ord(STime[17]) - $30) * 10 + (ord(STime[18]) - $30);
  result := Timestamp(Year, Month, Day, hour, min, sec);
end;

function RLWord(adr: integer): word;
begin
  result := PByte(adr + 1)^ + (PByte(adr)^ * $100);
end;

function RLInteger(adr: integer): integer;
begin
  result := PByte(adr + 3)^ + (PByte(adr + 2)^ * $100) +
    (PByte(adr + 1)^ * $10000) + (PByte(adr)^ * $1000000);
end;

begin
  GetModuleFileName(hInstance, @AppPath[0], MAX_PATH);
  ExtractFilePath(@AppPath);
  ReadPatterns;
end.