{}

{XXX: Thread vars & TLS}

const
  ThreadVarBlockSize: DWord = 0;
  TLSGrowFor = 4096;

type
  PTLSIndex = ^TTLSIndex;
  TTLSIndex = record
    CS: TRTLCriticalSection;
    Slots: array[0..TLSGrowFor - 1] of record
      TID: DWord;
      Value: Pointer;
    end;
  end;

var
  TLSKey: PTLSIndex;


function TLSAlloc(): PTLSIndex;
var
  I: DWord;
begin
  {New(Result);}
  Result := kos_alloc(SizeOf(TTLSIndex));
  InitCriticalSection(Result^.CS);
  {SetLength(Result^.Slots, TLSGrowFor);}
  for I := 0 to TLSGrowFor - 1 do
    Result^.Slots[I].TID := 0;
end;


function TLSFree(TLSIndex: PTLSIndex): Boolean;
begin
  DoneCriticalSection(TLSIndex^.CS);
  {SetLength(TLSIndex^.Slots, 0);
  Dispose(TLSIndex);}
  kos_free(TLSIndex);
  Result := True;
end;


procedure TLSSetValue(TLSIndex: PTLSIndex; Value: Pointer);
var
  TID, I, Count, Slot: DWord;
begin
  TID := GetCurrentThreadID();
  EnterCriticalSection(TLSIndex^.CS);

  Count := Length(TLSIndex^.Slots);
  Slot := Count;

  for I := 0 to Count - 1 do
  if TLSIndex^.Slots[I].TID = TID then
  begin
    Slot := I;
    Break;
  end else
  if TLSIndex^.Slots[I].TID = 0 then
    Slot := I;

  if Slot >= Count then
  begin
    Halt(123);
    {SetLength(TLSIndex^.Slots, Count + TLSGrowFor);
    FillChar(TLSIndex^.Slots[Count], TLSGrowFor * SizeOf(TLSIndex^.Slots[0]), #0);
    Slot := Count;}
  end;

  TLSIndex^.Slots[Slot].TID := TID;
  TLSIndex^.Slots[Slot].Value := Value;

  LeaveCriticalSection(TLSIndex^.CS);
end;


function TLSGetValue(TLSIndex: PTLSIndex): Pointer;
var
  TID, I, Count: DWord;
begin
  Result := nil;
  TID := GetCurrentThreadID();

  EnterCriticalSection(TLSIndex^.CS);

  Count := Length(TLSIndex^.Slots);

  for I := 0 to Count - 1 do
  if TLSIndex^.Slots[I].TID = TID then
  begin
    Result := TLSIndex^.Slots[I].Value;
    break;
  end;

  LeaveCriticalSection(TLSIndex^.CS);
end;


procedure SysInitThreadVar(var Offset: DWord; Size: DWord);
begin
  Offset := ThreadVarBlockSize;
  Inc(ThreadVarBlockSize, Size);
end;

procedure SysAllocateThreadVars;
var
  DataIndex: Pointer;
begin
  {DataIndex := GetMem(ThreadVarBlockSize);}
  DataIndex := kos_alloc(ThreadVarBlockSize);
  FillChar(DataIndex^, ThreadVarBlockSize, #0);
  TLSSetValue(TLSKey, DataIndex);
end;

function SysRelocateThreadVar(Offset: DWord): Pointer;
var
  DataIndex: Pointer;
begin
  DataIndex := TLSGetValue(TLSKey);
  if DataIndex = nil then
  begin
    SysAllocateThreadVars;
    DataIndex := TLSGetValue(TLSKey);
  end;
  Result := DataIndex + Offset;
end;

procedure SysReleaseThreadVars;
begin
  {FreeMem(TLSGetValue(TLSKey));}
  kos_free(TLSGetValue(TLSKey));
end;



{XXX: Thread}
type
  PThreadInfo = ^TThreadInfo;
  TThreadInfo = record
    Func: TThreadFunc;
    Arg: Pointer;
    StackSize: PtrUInt;
    Stack: Pointer;
  end;


procedure ThreadMain(ThreadInfo: PThreadInfo);
var
  Result: PtrInt;
begin
  SysAllocateThreadVars;
  with ThreadInfo^ do
  begin
    InitThread(StackSize);
    try
       Result := Func(Arg);
    except
      {TODO: Обработать ошибки}
      WriteLn(StdErr, 'Exception in thread');
    end;
    FreeMem(Stack);
  end;
  asm
    movl $-1, %eax
    int $0x40
  end;
end;

function SysBeginThread(sa: Pointer; StackSize: PtrUInt; ThreadFunction: TThreadFunc; Arg: Pointer; CreationFlags: DWord; var ThreadID: TThreadID): TThreadID;
{Stack, esp, ThreadInfo}

  procedure EntryThreadMain; assembler;
  asm
    movl %esp, %eax
    jmp ThreadMain
  end;

var
  Stack: Pointer;
  ThreadInfo: PThreadInfo;
begin
  if not IsMultiThread then
  begin
    TLSKey := TLSAlloc();
    InitThreadVars(@SysRelocateThreadVar);
    IsMultiThread := True;
  end;

  StackSize := (StackSize + 3) div 4;
  Stack := GetMem(StackSize + SizeOf(TThreadInfo));
  ThreadInfo := PThreadInfo(PByte(Stack) + StackSize);
  ThreadInfo^.Func := ThreadFunction;
  ThreadInfo^.Arg := Arg;
  ThreadInfo^.StackSize := StackSize;
  ThreadInfo^.Stack := Stack;
  ThreadID := kos_newthread(@EntryThreadMain, ThreadInfo);
  Result := ThreadID;
end;


procedure SysEndThread(ExitCode: DWord);
begin
  WriteLn('..SysEndThread');
  {TODO: SysEndThread}
  SysReleaseThreadVars;
end;


function SysSuspendThread(ThreadHandle: TThreadID): DWord;
begin
  {TODO: SysSuspendThread}
  Result := -1;
end;


function SysResumeThread(ThreadHandle: TThreadID): DWord;
begin
  {TODO: SysResumeThread}
  Result := -1;
end;


function SysKillThread(ThreadHandle: TThreadID): DWord;
begin
  if kos_killthread(ThreadHandle) then
    Result := 0 else
    Result := -1;
end;


procedure SysThreadSwitch;
begin
  {$ifdef EMULATOR}
  kos_delay(0);{$else}
  kos_switchthread();{$endif}
end;


function SysGetCurrentThreadID: TThreadID;
var
  ThreadInfo: TKosThreadInfo;
begin
  kos_threadinfo(@ThreadInfo);
  Result := ThreadInfo.ThreadID;
end;


{XXX: CriticalSection}
procedure SysInitCriticalSection(var CS);
begin
  PRTLCriticalSection(CS)^.OwningThread := -1;
end;

procedure SysDoneCriticalSection(var CS);
begin
  PRTLCriticalSection(CS)^.OwningThread := -1;
end;

procedure SysEnterCriticalSection(var CS);
var
  ThisThread: TThreadID;
begin
  ThisThread := GetCurrentThreadId();
  if PRTLCriticalSection(CS)^.OwningThread <> ThisThread then
    while PRTLCriticalSection(CS)^.OwningThread <> -1 do;
  PRTLCriticalSection(CS)^.OwningThread := ThisThread;
end;

procedure SysLeaveCriticalSection(var CS);
begin
  if PRTLCriticalSection(CS)^.OwningThread = GetCurrentThreadId() then
    PRTLCriticalSection(CS)^.OwningThread := -1;
end;


{TODO: RTLEvent}
function SysRTLEventCreate: PRTLEvent;
begin
  Result := nil;
end;

procedure SysRTLEventDestroy(State: PRTLEvent);
begin
end;


{*****************************************************************************
                           Heap Mutex Protection
*****************************************************************************}

{$ifndef HAS_MT_MEMORYMANAGER}
var
  HeapMutex: TRTLCriticalSection;

procedure KosHeapMutexInit;
begin
  InitCriticalSection(HeapMutex);
end;

procedure KosHeapMutexDone;
begin
  DoneCriticalSection(HeapMutex);
end;

procedure KosHeapMutexLock;
begin
  EnterCriticalSection(HeapMutex);
end;

procedure KosHeapMutexUnlock;
begin
  LeaveCriticalSection(HeapMutex);
end;


const
  KosMemoryMutexManager: TMemoryMutexManager = (
    MutexInit  : @KosHeapMutexInit;
    MutexDone  : @KosHeapMutexDone;
    MutexLock  : @KosHeapMutexLock;
    MutexUnlock: @KosHeapMutexUnlock);


procedure InitHeapMutexes;
begin
  SetMemoryMutexManager(KosMemoryMutexManager);
end;

{$endif HAS_MT_MEMORYMANAGER}


var
  KosThreadManager: TThreadManager;

procedure InitSystemThreads;
begin
  ThreadID := TThreadID(1);
  with KosThreadManager do
  begin
    InitManager            := nil;
    DoneManager            := nil;

    BeginThread            := @SysBeginThread;
    EndThread              := @SysEndThread;
    SuspendThread          := @SysSuspendThread;
    ResumeThread           := @SysResumeThread;
    KillThread             := @SysKillThread;
    ThreadSwitch           := @SysThreadSwitch;
    WaitForThreadTerminate := nil; //@NoWaitForThreadTerminate;
    ThreadSetPriority      := nil; //@NoThreadSetPriority;
    ThreadGetPriority      := nil; //@NoThreadGetPriority;

    GetCurrentThreadID     := @SysGetCurrentThreadID;
    InitCriticalSection    := @SysInitCriticalSection;
    DoneCriticalSection    := @SysDoneCriticalSection;
    EnterCriticalSection   := @SysEnterCriticalSection;
    LeaveCriticalSection   := @SysLeaveCriticalSection;
    InitThreadVar          := @SysInitThreadVar;
    RelocateThreadVar      := @SysRelocateThreadVar;
    AllocateThreadVars     := @SysAllocateThreadVars;
    ReleaseThreadVars      := @SysReleaseThreadVars;

    BasicEventCreate       := @NoBasicEventCreate;
    BasicEventDestroy      := @NoBasicEventDestroy;
    BasicEventResetEvent   := @NoBasicEventResetEvent;
    BasicEventSetEvent     := @NoBasicEventSetEvent;
    BasicEventWaitFor      := @NoBasicEventWaitFor;
    RTLEventCreate         := @SysRTLEventCreate;
    RTLEventDestroy        := @SysRTLEventDestroy;
    RTLEventSetEvent       := @NoRTLEventSetEvent;
    RTLEventWaitFor        := @NoRTLEventWaitFor;
    RTLEventSync           := @NoRTLEventSync;
    RTLEventWaitForTimeout := @NoRTLEventWaitForTimeout;

    SemaphoreInit          := @NoSemaphoreInit;
    SemaphoreDestroy       := @NoSemaphoreDestroy;
    SemaphoreWait          := @NoSemaphoreWait;
    SemaphorePost          := @NoSemaphorePost;
  end;
  SetThreadManager(KosThreadManager);
{$ifndef HAS_MT_MEMORYMANAGER}
  InitHeapMutexes;
{$endif HAS_MT_MEMORYMANAGER}
  ThreadID := GetCurrentThreadID;
end;
