/*
 * Fiber support
 *
 * Copyright 2002 Alexandre Julliard
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * FIXME:
 * - proper handling of 16-bit stack and signal stack
 */

#include "config.h"
#include "wine/port.h"

#include <setjmp.h>
#include <stdarg.h>

#define NONAMELESSUNION
#include "windef.h"
#include "winbase.h"
#include "winerror.h"
#include "winternl.h"
#include "wine/exception.h"
#include "wine/library.h"

struct fiber_data
{
    LPVOID                param;             /* 00 fiber param */
    void                 *except;            /* 04 saved exception handlers list */
    void                 *stack_base;        /* 08 top of fiber stack */
    void                 *stack_limit;       /* 0c fiber stack low-water mark */
    void                 *stack_allocation;  /* 10 base of the fiber stack allocation */
    sigjmp_buf            jmpbuf;            /* 14 setjmp buffer (on Windows: CONTEXT) */
    DWORD                 flags;             /*    fiber flags */
    LPFIBER_START_ROUTINE start;             /*    start routine */
};


/* call the fiber initial function once we have switched stack */
static void start_fiber( void *arg )
{
    struct fiber_data *fiber = arg;
    LPFIBER_START_ROUTINE start = fiber->start;

    __TRY
    {
        fiber->start = NULL;
        start( fiber->param );
        ExitThread( 1 );
    }
    __EXCEPT(UnhandledExceptionFilter)
    {
        TerminateThread( GetCurrentThread(), GetExceptionCode() );
    }
    __ENDTRY
}


/***********************************************************************
 *           CreateFiber   (KERNEL32.@)
 */
LPVOID WINAPI CreateFiber( SIZE_T stack, LPFIBER_START_ROUTINE start, LPVOID param )
{
    return CreateFiberEx( stack, 0, 0, start, param );
}


/***********************************************************************
 *           CreateFiberEx   (KERNEL32.@)
 */
LPVOID WINAPI CreateFiberEx( SIZE_T stack_commit, SIZE_T stack_reserve, DWORD flags,
                             LPFIBER_START_ROUTINE start, LPVOID param )
{
    struct fiber_data *fiber;

    if (!(fiber = HeapAlloc( GetProcessHeap(), 0, sizeof(*fiber) )))
    {
        SetLastError( ERROR_NOT_ENOUGH_MEMORY );
        return NULL;
    }

    /* FIXME: should use the thread stack allocation routines here */
    if (!stack_reserve) stack_reserve = 1024*1024;
    if(!(fiber->stack_allocation = VirtualAlloc( 0, stack_reserve, MEM_COMMIT, PAGE_READWRITE )))
    {
        HeapFree( GetProcessHeap(), 0, fiber );
        return NULL;
    }
    fiber->stack_base  = (char *)fiber->stack_allocation + stack_reserve;
    fiber->stack_limit = fiber->stack_allocation;
    fiber->param       = param;
    fiber->except      = (void *)-1;
    fiber->start       = start;
    fiber->flags       = flags;
    return fiber;
}


/***********************************************************************
 *           DeleteFiber   (KERNEL32.@)
 */
void WINAPI DeleteFiber( LPVOID fiber_ptr )
{
    struct fiber_data *fiber = fiber_ptr;

    if (!fiber) return;
    if (fiber == NtCurrentTeb()->Tib.u.FiberData)
    {
        HeapFree( GetProcessHeap(), 0, fiber );
        ExitThread(1);
    }
    VirtualFree( fiber->stack_allocation, 0, MEM_RELEASE );
    HeapFree( GetProcessHeap(), 0, fiber );
}


/***********************************************************************
 *           ConvertThreadToFiber   (KERNEL32.@)
 */
LPVOID WINAPI ConvertThreadToFiber( LPVOID param )
{
    return ConvertThreadToFiberEx( param, 0 );
}


/***********************************************************************
 *           ConvertThreadToFiberEx   (KERNEL32.@)
 */
LPVOID WINAPI ConvertThreadToFiberEx( LPVOID param, DWORD flags )
{
    struct fiber_data *fiber;

    if (!(fiber = HeapAlloc( GetProcessHeap(), 0, sizeof(*fiber) )))
    {
        SetLastError( ERROR_NOT_ENOUGH_MEMORY );
        return NULL;
    }
    fiber->param            = param;
    fiber->except           = NtCurrentTeb()->Tib.ExceptionList;
    fiber->stack_base       = NtCurrentTeb()->Tib.StackBase;
    fiber->stack_limit      = NtCurrentTeb()->Tib.StackLimit;
    fiber->stack_allocation = NtCurrentTeb()->DeallocationStack;
    fiber->start            = NULL;
    fiber->flags            = flags;
    NtCurrentTeb()->Tib.u.FiberData = fiber;
    return fiber;
}


/***********************************************************************
 *           ConvertFiberToThread   (KERNEL32.@)
 */
BOOL WINAPI ConvertFiberToThread(void)
{
    struct fiber_data *fiber = NtCurrentTeb()->Tib.u.FiberData;

    if (fiber)
    {
        NtCurrentTeb()->Tib.u.FiberData = NULL;
        HeapFree( GetProcessHeap(), 0, fiber );
    }
    return TRUE;
}


/***********************************************************************
 *           SwitchToFiber   (KERNEL32.@)
 */
void WINAPI SwitchToFiber( LPVOID fiber )
{
    struct fiber_data *new_fiber = fiber;
    struct fiber_data *current_fiber = NtCurrentTeb()->Tib.u.FiberData;

    current_fiber->except      = NtCurrentTeb()->Tib.ExceptionList;
    current_fiber->stack_limit = NtCurrentTeb()->Tib.StackLimit;
    /* stack_allocation and stack_base never change */

    /* FIXME: should save floating point context if requested in fiber->flags */
    if (!sigsetjmp( current_fiber->jmpbuf, 1 ))
    {
        NtCurrentTeb()->Tib.u.FiberData   = new_fiber;
        NtCurrentTeb()->Tib.ExceptionList = new_fiber->except;
        NtCurrentTeb()->Tib.StackBase     = new_fiber->stack_base;
        NtCurrentTeb()->Tib.StackLimit    = new_fiber->stack_limit;
        NtCurrentTeb()->DeallocationStack = new_fiber->stack_allocation;
        if (new_fiber->start)  /* first time */
            wine_switch_to_stack( start_fiber, new_fiber, new_fiber->stack_base );
        else
            siglongjmp( new_fiber->jmpbuf, 1 );
    }
}