/*
 * Copyright 2014 Dmitry Timoshkov
 *
 * 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 "winreg.h"
#include "objbase.h"
#include "taskschd.h"
#include "schrpc.h"
#include "taskschd_private.h"

#include "wine/unicode.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(taskschd);

typedef struct
{
    ITaskFolder ITaskFolder_iface;
    LONG ref;
    WCHAR *path;
} TaskFolder;

static inline TaskFolder *impl_from_ITaskFolder(ITaskFolder *iface)
{
    return CONTAINING_RECORD(iface, TaskFolder, ITaskFolder_iface);
}

static ULONG WINAPI TaskFolder_AddRef(ITaskFolder *iface)
{
    TaskFolder *folder = impl_from_ITaskFolder(iface);
    return InterlockedIncrement(&folder->ref);
}

static ULONG WINAPI TaskFolder_Release(ITaskFolder *iface)
{
    TaskFolder *folder = impl_from_ITaskFolder(iface);
    LONG ref = InterlockedDecrement(&folder->ref);

    if (!ref)
    {
        TRACE("destroying %p\n", iface);
        heap_free(folder->path);
        heap_free(folder);
    }

    return ref;
}

static HRESULT WINAPI TaskFolder_QueryInterface(ITaskFolder *iface, REFIID riid, void **obj)
{
    if (!riid || !obj) return E_INVALIDARG;

    TRACE("%p,%s,%p\n", iface, debugstr_guid(riid), obj);

    if (IsEqualGUID(riid, &IID_ITaskFolder) ||
        IsEqualGUID(riid, &IID_IDispatch) ||
        IsEqualGUID(riid, &IID_IUnknown))
    {
        ITaskFolder_AddRef(iface);
        *obj = iface;
        return S_OK;
    }

    FIXME("interface %s is not implemented\n", debugstr_guid(riid));
    *obj = NULL;
    return E_NOINTERFACE;
}

static HRESULT WINAPI TaskFolder_GetTypeInfoCount(ITaskFolder *iface, UINT *count)
{
    FIXME("%p,%p: stub\n", iface, count);
    return E_NOTIMPL;
}

static HRESULT WINAPI TaskFolder_GetTypeInfo(ITaskFolder *iface, UINT index, LCID lcid, ITypeInfo **info)
{
    FIXME("%p,%u,%u,%p: stub\n", iface, index, lcid, info);
    return E_NOTIMPL;
}

static HRESULT WINAPI TaskFolder_GetIDsOfNames(ITaskFolder *iface, REFIID riid, LPOLESTR *names,
                                                UINT count, LCID lcid, DISPID *dispid)
{
    FIXME("%p,%s,%p,%u,%u,%p: stub\n", iface, debugstr_guid(riid), names, count, lcid, dispid);
    return E_NOTIMPL;
}

static HRESULT WINAPI TaskFolder_Invoke(ITaskFolder *iface, DISPID dispid, REFIID riid, LCID lcid, WORD flags,
                                         DISPPARAMS *params, VARIANT *result, EXCEPINFO *excepinfo, UINT *argerr)
{
    FIXME("%p,%d,%s,%04x,%04x,%p,%p,%p,%p: stub\n", iface, dispid, debugstr_guid(riid), lcid, flags,
          params, result, excepinfo, argerr);
    return E_NOTIMPL;
}

static HRESULT WINAPI TaskFolder_get_Name(ITaskFolder *iface, BSTR *name)
{
    TaskFolder *folder = impl_from_ITaskFolder(iface);
    const WCHAR *p_name;

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

    if (!name) return E_POINTER;

    p_name = strrchrW(folder->path, '\\');
    if (!p_name)
        p_name = folder->path;
    else
        if (p_name[1] != 0) p_name++;

    *name = SysAllocString(p_name);
    if (!*name) return E_OUTOFMEMORY;

    return S_OK;
}

static HRESULT WINAPI TaskFolder_get_Path(ITaskFolder *iface, BSTR *path)
{
    TaskFolder *folder = impl_from_ITaskFolder(iface);

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

    if (!path) return E_POINTER;

    *path = SysAllocString(folder->path);
    if (!*path) return E_OUTOFMEMORY;

    return S_OK;
}

static HRESULT WINAPI TaskFolder_GetFolder(ITaskFolder *iface, BSTR path, ITaskFolder **new_folder)
{
    TaskFolder *folder = impl_from_ITaskFolder(iface);

    TRACE("%p,%s,%p\n", iface, debugstr_w(path), folder);

    if (!path) return E_INVALIDARG;
    if (!new_folder) return E_POINTER;

    return TaskFolder_create(folder->path, path, new_folder, FALSE);
}

static HRESULT WINAPI TaskFolder_GetFolders(ITaskFolder *iface, LONG flags, ITaskFolderCollection **folders)
{
    TaskFolder *folder = impl_from_ITaskFolder(iface);

    TRACE("%p,%x,%p: stub\n", iface, flags, folders);

    if (!folders) return E_POINTER;

    if (flags)
        FIXME("unsupported flags %x\n", flags);

    return TaskFolderCollection_create(folder->path, folders);
}

static inline BOOL is_variant_null(const VARIANT *var)
{
    return V_VT(var) == VT_EMPTY || V_VT(var) == VT_NULL ||
          (V_VT(var) == VT_BSTR && (V_BSTR(var) == NULL || !*V_BSTR(var)));
}

static HRESULT WINAPI TaskFolder_CreateFolder(ITaskFolder *iface, BSTR path, VARIANT sddl, ITaskFolder **new_folder)
{
    TaskFolder *folder = impl_from_ITaskFolder(iface);
    ITaskFolder *tmp_folder = NULL;
    HRESULT hr;

    TRACE("%p,%s,%s,%p\n", iface, debugstr_w(path), debugstr_variant(&sddl), folder);

    if (!path) return E_INVALIDARG;

    if (!new_folder) new_folder = &tmp_folder;

    if (!is_variant_null(&sddl))
        FIXME("security descriptor %s is ignored\n", debugstr_variant(&sddl));

    hr = TaskFolder_create(folder->path, path, new_folder, TRUE);
    if (tmp_folder)
        ITaskFolder_Release(tmp_folder);

    return hr;
}

WCHAR *get_full_path(const WCHAR *parent, const WCHAR *path)
{
    static const WCHAR bslash[] = { '\\', 0 };
    WCHAR *folder_path;
    int len = 0;

    if (path) len = strlenW(path);

    if (parent) len += strlenW(parent);

    /* +1 if parent is not '\' terminated */
    folder_path = heap_alloc((len + 2) * sizeof(WCHAR));
    if (!folder_path) return NULL;

    folder_path[0] = 0;

    if (parent)
        strcpyW(folder_path, parent);

    if (path && *path)
    {
        len = strlenW(folder_path);
        if (!len || folder_path[len - 1] != '\\')
            strcatW(folder_path, bslash);

        while (*path == '\\') path++;
        strcatW(folder_path, path);
    }

    len = strlenW(folder_path);
    if (!len)
        strcatW(folder_path, bslash);

    return folder_path;
}

static HRESULT WINAPI TaskFolder_DeleteFolder(ITaskFolder *iface, BSTR name, LONG flags)
{
    TaskFolder *folder = impl_from_ITaskFolder(iface);
    WCHAR *folder_path;
    HRESULT hr;

    TRACE("%p,%s,%x\n", iface, debugstr_w(name), flags);

    if (!name || !*name) return E_ACCESSDENIED;

    if (flags)
        FIXME("unsupported flags %x\n", flags);

    folder_path = get_full_path(folder->path, name);
    if (!folder_path) return E_OUTOFMEMORY;

    hr = SchRpcDelete(folder_path, 0);
    heap_free(folder_path);
    return hr;
}

static HRESULT WINAPI TaskFolder_GetTask(ITaskFolder *iface, BSTR name, IRegisteredTask **task)
{
    TaskFolder *folder = impl_from_ITaskFolder(iface);
    ITaskDefinition *taskdef;
    HRESULT hr;

    TRACE("%p,%s,%p\n", iface, debugstr_w(name), task);

    if (!task) return E_POINTER;

    hr = TaskDefinition_create(&taskdef);
    if (hr != S_OK) return hr;

    hr = RegisteredTask_create(folder->path, name, taskdef, 0, 0, task, FALSE);
    if (hr != S_OK)
        ITaskDefinition_Release(taskdef);
    return hr;
}

static HRESULT WINAPI TaskFolder_GetTasks(ITaskFolder *iface, LONG flags, IRegisteredTaskCollection **tasks)
{
    TaskFolder *folder = impl_from_ITaskFolder(iface);

    TRACE("%p,%x,%p: stub\n", iface, flags, tasks);

    if (!tasks) return E_POINTER;

    return RegisteredTaskCollection_create(folder->path, tasks);
}

static HRESULT WINAPI TaskFolder_DeleteTask(ITaskFolder *iface, BSTR name, LONG flags)
{
    TaskFolder *folder = impl_from_ITaskFolder(iface);
    WCHAR *folder_path;
    HRESULT hr;

    TRACE("%p,%s,%x\n", iface, debugstr_w(name), flags);

    if (!name || !*name) return E_ACCESSDENIED;

    if (flags)
        FIXME("unsupported flags %x\n", flags);

    folder_path = get_full_path(folder->path, name);
    if (!folder_path) return E_OUTOFMEMORY;

    hr = SchRpcDelete(folder_path, 0);
    heap_free(folder_path);
    return hr;
}

static HRESULT WINAPI TaskFolder_RegisterTask(ITaskFolder *iface, BSTR name, BSTR xml, LONG flags,
                                              VARIANT user, VARIANT password, TASK_LOGON_TYPE logon,
                                              VARIANT sddl, IRegisteredTask **task)
{
    TaskFolder *folder = impl_from_ITaskFolder(iface);
    IRegisteredTask *regtask = NULL;
    ITaskDefinition *taskdef;
    HRESULT hr;

    TRACE("%p,%s,%s,%x,%s,%s,%d,%s,%p\n", iface, debugstr_w(name), debugstr_w(xml), flags,
          debugstr_variant(&user), debugstr_variant(&password), logon, debugstr_variant(&sddl), task);

    if (!xml) return HRESULT_FROM_WIN32(RPC_X_NULL_REF_POINTER);

    if (!task) task = &regtask;

    hr = TaskDefinition_create(&taskdef);
    if (hr != S_OK) return hr;

    hr = ITaskDefinition_put_XmlText(taskdef, xml);
    if (hr == S_OK)
        hr = RegisteredTask_create(folder->path, name, taskdef, flags, logon, task, TRUE);

    if (hr != S_OK)
        ITaskDefinition_Release(taskdef);

    if (regtask)
        IRegisteredTask_Release(regtask);

    return hr;
}

static HRESULT WINAPI TaskFolder_RegisterTaskDefinition(ITaskFolder *iface, BSTR name, ITaskDefinition *definition, LONG flags,
                                                        VARIANT user, VARIANT password, TASK_LOGON_TYPE logon,
                                                        VARIANT sddl, IRegisteredTask **task)
{
    TaskFolder *folder = impl_from_ITaskFolder(iface);
    IRegisteredTask *regtask = NULL;
    HRESULT hr;

    FIXME("%p,%s,%p,%x,%s,%s,%d,%s,%p: stub\n", iface, debugstr_w(name), definition, flags,
          debugstr_variant(&user), debugstr_variant(&password), logon, debugstr_variant(&sddl), task);

    if (!is_variant_null(&sddl))
        FIXME("security descriptor %s is ignored\n", debugstr_variant(&sddl));

    if (!is_variant_null(&user) || !is_variant_null(&password))
        FIXME("user/password are ignored\n");

    if (!task) task = &regtask;

    ITaskDefinition_AddRef(definition);
    hr = RegisteredTask_create(folder->path, name, definition, flags, logon, task, TRUE);
    if (hr != S_OK)
        ITaskDefinition_Release(definition);

    if (regtask)
        IRegisteredTask_Release(regtask);

    return hr;
}

static HRESULT WINAPI TaskFolder_GetSecurityDescriptor(ITaskFolder *iface, LONG info, BSTR *sddl)
{
    FIXME("%p,%x,%p: stub\n", iface, info, sddl);
    return E_NOTIMPL;
}

static HRESULT WINAPI TaskFolder_SetSecurityDescriptor(ITaskFolder *iface, BSTR sddl, LONG flags)
{
    FIXME("%p,%s,%x: stub\n", iface, debugstr_w(sddl), flags);
    return E_NOTIMPL;
}

static const ITaskFolderVtbl TaskFolder_vtbl =
{
    TaskFolder_QueryInterface,
    TaskFolder_AddRef,
    TaskFolder_Release,
    TaskFolder_GetTypeInfoCount,
    TaskFolder_GetTypeInfo,
    TaskFolder_GetIDsOfNames,
    TaskFolder_Invoke,
    TaskFolder_get_Name,
    TaskFolder_get_Path,
    TaskFolder_GetFolder,
    TaskFolder_GetFolders,
    TaskFolder_CreateFolder,
    TaskFolder_DeleteFolder,
    TaskFolder_GetTask,
    TaskFolder_GetTasks,
    TaskFolder_DeleteTask,
    TaskFolder_RegisterTask,
    TaskFolder_RegisterTaskDefinition,
    TaskFolder_GetSecurityDescriptor,
    TaskFolder_SetSecurityDescriptor
};

HRESULT TaskFolder_create(const WCHAR *parent, const WCHAR *path, ITaskFolder **obj, BOOL create)
{
    TaskFolder *folder;
    WCHAR *folder_path;
    HRESULT hr;

    if (path)
    {
        int len = strlenW(path);
        if (len && path[len - 1] == '\\') return HRESULT_FROM_WIN32(ERROR_INVALID_NAME);
    }

    folder_path = get_full_path(parent, path);
    if (!folder_path) return E_OUTOFMEMORY;

    if (create)
    {
        hr = SchRpcCreateFolder(folder_path, NULL, 0);
    }
    else
    {
        DWORD start_index, count, i;
        TASK_NAMES names;

        start_index = 0;
        names = NULL;
        hr = SchRpcEnumFolders(folder_path, 0, &start_index, 0, &count, &names);
        if (hr == S_OK)
        {
            for (i = 0; i < count; i++)
                MIDL_user_free(names[i]);
            MIDL_user_free(names);
        }
        else
        {
            if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
                hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
        }
    }

    if (FAILED(hr))
    {
        heap_free(folder_path);
        return hr;
    }

    folder = heap_alloc(sizeof(*folder));
    if (!folder)
    {
        heap_free(folder_path);
        return E_OUTOFMEMORY;
    }

    folder->ITaskFolder_iface.lpVtbl = &TaskFolder_vtbl;
    folder->ref = 1;
    folder->path = folder_path;
    *obj = &folder->ITaskFolder_iface;

    TRACE("created %p\n", *obj);

    return S_OK;
}
