/*
 * Copyright (C) 2008 Google (Roy Shea)
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdarg.h>

#define COBJMACROS

#include "windef.h"
#include "winbase.h"
#include "objbase.h"
#include "winternl.h"
#include "taskschd.h"
#include "mstask.h"
#include "mstask_private.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(mstask);

typedef struct
{
    ITaskTrigger ITaskTrigger_iface;
    LONG ref;
    TASK_TRIGGER triggerCond;
} TaskTriggerImpl;

static inline TaskTriggerImpl *impl_from_ITaskTrigger(ITaskTrigger *iface)
{
    return CONTAINING_RECORD(iface, TaskTriggerImpl, ITaskTrigger_iface);
}

static HRESULT WINAPI MSTASK_ITaskTrigger_QueryInterface(
        ITaskTrigger* iface,
        REFIID riid,
        void **ppvObject)
{
    TaskTriggerImpl *This = impl_from_ITaskTrigger(iface);

    TRACE("IID: %s\n", debugstr_guid(riid));
    if (ppvObject == NULL)
        return E_POINTER;

    if (IsEqualGUID(riid, &IID_IUnknown) ||
            IsEqualGUID(riid, &IID_ITaskTrigger))
    {
        *ppvObject = &This->ITaskTrigger_iface;
        ITaskTrigger_AddRef(iface);
        return S_OK;
    }

    WARN("Unknown interface: %s\n", debugstr_guid(riid));
    *ppvObject = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI MSTASK_ITaskTrigger_AddRef(
        ITaskTrigger* iface)
{
    TaskTriggerImpl *This = impl_from_ITaskTrigger(iface);
    ULONG ref;
    TRACE("\n");
    ref = InterlockedIncrement(&This->ref);
    return ref;
}

static ULONG WINAPI MSTASK_ITaskTrigger_Release(
        ITaskTrigger* iface)
{
    TaskTriggerImpl *This = impl_from_ITaskTrigger(iface);
    ULONG ref;
    TRACE("\n");
    ref = InterlockedDecrement(&This->ref);
    if (ref == 0)
    {
        HeapFree(GetProcessHeap(), 0, This);
        InterlockedDecrement(&dll_ref);
    }
    return ref;
}

static HRESULT WINAPI MSTASK_ITaskTrigger_SetTrigger(
        ITaskTrigger* iface,
        const PTASK_TRIGGER pTrigger)
{
    TaskTriggerImpl * This = impl_from_ITaskTrigger(iface);
    TIME_FIELDS field_time;
    LARGE_INTEGER sys_time;
    TASK_TRIGGER tmp_trigger_cond;

    TRACE("(%p, %p)\n", iface, pTrigger);

    /* Verify valid structure size */
    if (pTrigger->cbTriggerSize != sizeof(*pTrigger))
        return E_INVALIDARG;
    tmp_trigger_cond.cbTriggerSize = pTrigger->cbTriggerSize;

    /* Reserved field must be zero */
    tmp_trigger_cond.Reserved1 = 0;

    /* Verify and set valid start date and time */
    memset(&field_time, 0, sizeof(field_time));
    field_time.Year = pTrigger->wBeginYear;
    field_time.Month = pTrigger->wBeginMonth;
    field_time.Day = pTrigger->wBeginDay;
    field_time.Hour = pTrigger->wStartHour;
    field_time.Minute = pTrigger->wStartMinute;
    if (!RtlTimeFieldsToTime(&field_time, &sys_time))
        return E_INVALIDARG;
    tmp_trigger_cond.wBeginYear = pTrigger->wBeginYear;
    tmp_trigger_cond.wBeginMonth = pTrigger->wBeginMonth;
    tmp_trigger_cond.wBeginDay = pTrigger->wBeginDay;
    tmp_trigger_cond.wStartHour = pTrigger->wStartHour;
    tmp_trigger_cond.wStartMinute = pTrigger->wStartMinute;

    /* Verify valid end date if TASK_TRIGGER_FLAG_HAS_END_DATE flag is set */
    if (pTrigger->rgFlags & TASK_TRIGGER_FLAG_HAS_END_DATE)
    {
        memset(&field_time, 0, sizeof(field_time));
        field_time.Year = pTrigger->wEndYear;
        field_time.Month = pTrigger->wEndMonth;
        field_time.Day = pTrigger->wEndDay;
        if (!RtlTimeFieldsToTime(&field_time, &sys_time))
            return E_INVALIDARG;
    }

    /* Set valid end date independent of TASK_TRIGGER_FLAG_HAS_END_DATE flag */
    tmp_trigger_cond.wEndYear = pTrigger->wEndYear;
    tmp_trigger_cond.wEndMonth = pTrigger->wEndMonth;
    tmp_trigger_cond.wEndDay = pTrigger->wEndDay;

    /* Verify duration and interval pair */
    if (pTrigger->MinutesDuration <= pTrigger->MinutesInterval &&
            pTrigger->MinutesInterval > 0)
        return E_INVALIDARG;
    tmp_trigger_cond.MinutesDuration = pTrigger->MinutesDuration;
    tmp_trigger_cond.MinutesInterval = pTrigger->MinutesInterval;

    /* Copy over flags */
    tmp_trigger_cond.rgFlags = pTrigger->rgFlags;

    /* Set TriggerType dependent fields of Type union */
    tmp_trigger_cond.TriggerType = pTrigger->TriggerType;
    switch (pTrigger->TriggerType)
    {
        case TASK_TIME_TRIGGER_DAILY:
            tmp_trigger_cond.Type.Daily.DaysInterval =
                    pTrigger->Type.Daily.DaysInterval;
            break;
        case TASK_TIME_TRIGGER_WEEKLY:
            tmp_trigger_cond.Type.Weekly.WeeksInterval =
                    pTrigger->Type.Weekly.WeeksInterval;
            tmp_trigger_cond.Type.Weekly.rgfDaysOfTheWeek =
                    pTrigger->Type.Weekly.rgfDaysOfTheWeek;
            break;
        case TASK_TIME_TRIGGER_MONTHLYDATE:
            tmp_trigger_cond.Type.MonthlyDate.rgfDays =
                    pTrigger->Type.MonthlyDate.rgfDays;
            tmp_trigger_cond.Type.MonthlyDate.rgfMonths =
                    pTrigger->Type.MonthlyDate.rgfMonths;
            break;
        case TASK_TIME_TRIGGER_MONTHLYDOW:
            tmp_trigger_cond.Type.MonthlyDOW.wWhichWeek =
                    pTrigger->Type.MonthlyDOW.wWhichWeek;
            tmp_trigger_cond.Type.MonthlyDOW.rgfDaysOfTheWeek =
                    pTrigger->Type.MonthlyDOW.rgfDaysOfTheWeek;
            tmp_trigger_cond.Type.MonthlyDOW.rgfMonths =
                    pTrigger->Type.MonthlyDOW.rgfMonths;
            break;
        case TASK_TIME_TRIGGER_ONCE:
        case TASK_EVENT_TRIGGER_ON_IDLE:
        case TASK_EVENT_TRIGGER_AT_SYSTEMSTART:
        case TASK_EVENT_TRIGGER_AT_LOGON:
        default:
            tmp_trigger_cond.Type = This->triggerCond.Type;
            break;
    }

    /* Reserved field must be zero */
    tmp_trigger_cond.Reserved2 = 0;

    /* wRandomMinutesInterval not currently used and is initialized to zero */
    tmp_trigger_cond.wRandomMinutesInterval = 0;

    /* Update object copy of triggerCond */
    This->triggerCond = tmp_trigger_cond;

    return S_OK;
}

static HRESULT WINAPI MSTASK_ITaskTrigger_GetTrigger(
        ITaskTrigger* iface,
        PTASK_TRIGGER pTrigger)
{
    TaskTriggerImpl * This = impl_from_ITaskTrigger(iface);

    TRACE("(%p, %p)\n", iface, pTrigger);

    /* Native implementation doesn't verify equivalent cbTriggerSize fields */

    /* Copy relevant fields of the structure */
    pTrigger->cbTriggerSize = This->triggerCond.cbTriggerSize;
    pTrigger->Reserved1 = 0;
    pTrigger->wBeginYear = This->triggerCond.wBeginYear;
    pTrigger->wBeginMonth = This->triggerCond.wBeginMonth;
    pTrigger->wBeginDay = This->triggerCond.wBeginDay;
    pTrigger->wEndYear = This->triggerCond.wEndYear;
    pTrigger->wEndMonth = This->triggerCond.wEndMonth;
    pTrigger->wEndDay = This->triggerCond.wEndDay;
    pTrigger->wStartHour = This->triggerCond.wStartHour;
    pTrigger->wStartMinute = This->triggerCond.wStartMinute;
    pTrigger->MinutesDuration = This->triggerCond.MinutesDuration;
    pTrigger->MinutesInterval = This->triggerCond.MinutesInterval;
    pTrigger->rgFlags = This->triggerCond.rgFlags;
    pTrigger->TriggerType = This->triggerCond.TriggerType;
    switch (This->triggerCond.TriggerType)
    {
        case TASK_TIME_TRIGGER_DAILY:
            pTrigger->Type.Daily.DaysInterval =
                    This->triggerCond.Type.Daily.DaysInterval;
            break;
        case TASK_TIME_TRIGGER_WEEKLY:
            pTrigger->Type.Weekly.WeeksInterval =
                    This->triggerCond.Type.Weekly.WeeksInterval;
            pTrigger->Type.Weekly.rgfDaysOfTheWeek =
                    This->triggerCond.Type.Weekly.rgfDaysOfTheWeek;
            break;
        case TASK_TIME_TRIGGER_MONTHLYDATE:
            pTrigger->Type.MonthlyDate.rgfDays =
                    This->triggerCond.Type.MonthlyDate.rgfDays;
            pTrigger->Type.MonthlyDate.rgfMonths =
                    This->triggerCond.Type.MonthlyDate.rgfMonths;
            break;
        case TASK_TIME_TRIGGER_MONTHLYDOW:
            pTrigger->Type.MonthlyDOW.wWhichWeek =
                    This->triggerCond.Type.MonthlyDOW.wWhichWeek;
            pTrigger->Type.MonthlyDOW.rgfDaysOfTheWeek =
                    This->triggerCond.Type.MonthlyDOW.rgfDaysOfTheWeek;
            pTrigger->Type.MonthlyDOW.rgfMonths =
                    This->triggerCond.Type.MonthlyDOW.rgfMonths;
            break;
        case TASK_TIME_TRIGGER_ONCE:
        case TASK_EVENT_TRIGGER_ON_IDLE:
        case TASK_EVENT_TRIGGER_AT_SYSTEMSTART:
        case TASK_EVENT_TRIGGER_AT_LOGON:
        default:
            break;
    }
    pTrigger->Reserved2 = 0;
    pTrigger->wRandomMinutesInterval = 0;
    return S_OK;
}

static HRESULT WINAPI MSTASK_ITaskTrigger_GetTriggerString(
        ITaskTrigger* iface,
        LPWSTR *ppwszTrigger)
{
    FIXME("Not implemented\n");
    return E_NOTIMPL;
}

static const ITaskTriggerVtbl MSTASK_ITaskTriggerVtbl =
{
    MSTASK_ITaskTrigger_QueryInterface,
    MSTASK_ITaskTrigger_AddRef,
    MSTASK_ITaskTrigger_Release,
    MSTASK_ITaskTrigger_SetTrigger,
    MSTASK_ITaskTrigger_GetTrigger,
    MSTASK_ITaskTrigger_GetTriggerString
};

HRESULT TaskTriggerConstructor(LPVOID *ppObj)
{
    TaskTriggerImpl *This;
    SYSTEMTIME time;
    TRACE("(%p)\n", ppObj);

    This = HeapAlloc(GetProcessHeap(), 0, sizeof(*This));
    if (!This)
        return E_OUTOFMEMORY;

    This->ITaskTrigger_iface.lpVtbl = &MSTASK_ITaskTriggerVtbl;
    This->ref = 1;

    /* Most fields of triggerCond default to zero.  Initialize other
     * fields to default values. */
    memset(&This->triggerCond, 0, sizeof(TASK_TRIGGER));
    GetLocalTime(&time);
    This->triggerCond.cbTriggerSize = sizeof(This->triggerCond);
    This->triggerCond.wBeginYear = time.wYear;
    This->triggerCond.wBeginMonth = time.wMonth;
    This->triggerCond.wBeginDay = time.wDay;
    This->triggerCond.wStartHour = time.wHour;
    This->triggerCond.wStartMinute = time.wMinute;
    This->triggerCond.rgFlags = TASK_TRIGGER_FLAG_DISABLED;
    This->triggerCond.TriggerType = TASK_TIME_TRIGGER_DAILY,
    This->triggerCond.Type.Daily.DaysInterval = 1;

    *ppObj = &This->ITaskTrigger_iface;
    InterlockedIncrement(&dll_ref);
    return S_OK;
}
