/**
 * Set a Task into task scheduler. It takes values to insert from Schedule form.
 * If task already exists, it is updated with passed parameters.
 * Starting time is always set to current time (task starts now).
 *
 * @param frequency : NEVER or EVERY_DAY
 * @param dayNum    : repeate the task every 'dayNum'
 * @param minNum    : repeate the task every 'minNum'
 * @return            0 if no errors.
 */
int setScheduleTask (const char* frequency, const int dayNum, const int minNum) {

    HRESULT hr = S_OK;
    bool newTask = false;
    ITaskScheduler* pITS = NULL;
    ITask*          pITask;
    IPersistFile*   pIPersistFile;
    wstring taskName, user;

    // Init COM library & create instance for Task scheduler.
    if ((pITS = initScheduleInstance()) == NULL) {
        goto error;
    }

    // Task is associated with current user
    if (getScheduledTaskName(taskName)) {
        goto error;
    }

    // We need the extended username "MACHINE\USER" to ensure that
    // the job can run also for users logged in with another domain (ref bug #307010).
    if (getWindowsUserEx(user)) {
        goto error;
    }

    //
    // Try to open EXISTING TASK.
    //
    LOG.debug("Try opening the Windows task: \"%ls\"", taskName.c_str());
    hr = pITS->Activate(taskName.c_str(),
                        IID_ITask,
                        (IUnknown**) &pITask);
    
    if (FAILED(hr)) {
        if (hr == E_OBJECT_NOT_FOUND) {
            //
            // NEW TASK -> create it.
            //
            LOG.debug(DBG_SCHED_TASK_NOT_FOUND);
            newTask = true;

            WCHAR path[512];
            OutlookConfig* config = OutlookConfig::getInstance();
            // Get current working dir (from config)
            const char* t = config->getWorkingDir();
            WCHAR* wt = toWideChar(t);
            wsprintf(path, TEXT("\"%s\\%s\""), wt, TEXT(PROGRAM_NAME_EXE));
            delete [] wt;  wt = NULL;

            hr = pITS->NewWorkItem(taskName.c_str(),       // Name of task
                                   CLSID_CTask,            // Class identifier
                                   IID_ITask,              // Interface identifier
                                   (IUnknown**)&pITask);   // Address of task interface
            if (FAILED(hr)) {
                char* msg = readSystemErrorMsg(hr);
                setErrorF(getLastErrorCode(), ERR_SCHED_NEWWORKITEM, hr, msg);
                delete [] msg;
                pITS->Release();
                goto error;
            }

            // Set path and comment on new task.
            pITask->SetApplicationName(path);
            pITask->SetComment(SCHED_COMMENT);
        }
        else {
            char* msg = readSystemErrorMsg(hr);
            setErrorF(getLastErrorCode(), ERR_SCHED_ACTIVATE, hr, msg);
            delete [] msg;
            pITS->Release();
            goto error;
        }
    }
    pITS->Release();


    //
    // Set task params: flags, account info, parameters
    if (strcmp(NEVER, frequency) == 0) {
        pITask->SetFlags(TASK_FLAG_DISABLED);
    } 
    else {
        pITask->SetAccountInformation(user.c_str(), NULL);
        pITask->SetFlags(TASK_FLAG_INTERACTIVE | TASK_FLAG_RUN_ONLY_IF_LOGGED_ON);
    }

    // ...so we have "OutlookPlugin.exe schedule"    
    pITask->SetParameters(SCHED_PARAM);


    //
    // Get/create the trigger object.
    //
    ITaskTrigger *pITaskTrigger;
    if (newTask) {
        WORD piNewTrigger;
        hr = pITask->CreateTrigger(&piNewTrigger, &pITaskTrigger);
        if (FAILED(hr)) {
            char* msg = readSystemErrorMsg(hr);
            setErrorF(getLastErrorCode(), ERR_SCHED_CREATE_TRIGGER, hr, msg);
            delete [] msg;
            goto error;
        }
    }
    else {
        hr = pITask->GetTrigger(0, &pITaskTrigger);
        if (FAILED(hr)) {
            char* msg = readSystemErrorMsg(hr);
            setErrorF(getLastErrorCode(), ERR_SCHED_GET_TRIGGER, hr, msg);
            delete [] msg;
            goto error;
        }
    }

    //////////////////////////////////////////////////////
    // Define TASK_TRIGGER structure.
    // Start time is derived from NOW.
    //////////////////////////////////////////////////////
    TASK_TRIGGER pTrigger;
    ZeroMemory(&pTrigger, sizeof (TASK_TRIGGER));

    time_t timer;
    time(&timer);
    struct tm* now = localtime(&timer);

    int minToSet = minNum;
    if (minToSet == 1440) {
        minToSet = 1439;   // this because the windows scheduler thrown an error if
                            // the value is a day
    }

    pTrigger.wBeginDay                = now->tm_mday;               // START = NOW.
    // TASK_TRIGGER uses wBeginMonth in interval (1,12), but tm_mon is in interval (0,11)
    pTrigger.wBeginMonth              = now->tm_mon + 1;
    pTrigger.wBeginYear               = now->tm_year + 1900;        // 'struct tm' has year from 1900...
    pTrigger.wStartHour               = now->tm_hour;
    pTrigger.wStartMinute             = now->tm_min - 1;            // -1 to avoid starting a sync right now!

    pTrigger.cbTriggerSize = sizeof (TASK_TRIGGER) ;
    pTrigger.MinutesDuration          = 1440;                       // Duration = fixed 1 day.
    pTrigger.MinutesInterval          = minToSet;
    pTrigger.TriggerType              = TASK_TIME_TRIGGER_DAILY;    // Manage only DAILY schedules!
    pTrigger.Type.Daily.DaysInterval  = dayNum;
    //////////////////////////////////////////////////////

    //
    // Set trigger criteria.
    //
    hr = pITaskTrigger->SetTrigger(&pTrigger);
    if (FAILED(hr)) {
        if (hr == E_INVALIDARG) {
            setErrorF(getLastErrorCode(), ERR_SCHED_SET_TRIGGER, hr, ERR_SCHED_INVALID_PARAM);
        }
        else {
            char* msg = readSystemErrorMsg(hr);
            setErrorF(getLastErrorCode(), ERR_SCHED_SET_TRIGGER, hr, msg);
            delete [] msg;
        }
        goto error;
    }

    //
    // Call IUnknown::QueryInterface to get a pointer to IPersistFile.
    //
    hr = pITask->QueryInterface(IID_IPersistFile, (void **)&pIPersistFile);
    if (FAILED(hr)) {
        char* msg = readSystemErrorMsg(hr);
        setErrorF(getLastErrorCode(), ERR_SCHED_QUERY_INTERFACE, hr, msg);
        delete [] msg;
        goto error;
    }
    pITask->Release();
    pITaskTrigger->Release();

    //
    // Save the task to disk.
    //
    hr = pIPersistFile->Save(NULL, TRUE);
    if (FAILED(hr)) {
        char* msg = readSystemErrorMsg(hr);
        setErrorF(getLastErrorCode(), ERR_SCHED_SAVE, hr, msg);
        delete [] msg;
        goto error;
    }
    pIPersistFile->Release();

    LOG.info(INFO_SCHED_TASK_CREATED);
    CoUninitialize();
    return 0;


error:
    CoUninitialize();
    LOG.error(getLastErrorMsg());
    return 1;
}
/**
 * Get task info from Task scheduler.
 * Returns 0 if task found and no errors, -1 if task not found.
 * If task found: frequency, dayNum, minNum and statusCode are set.
 *
 * @param active  : [OUT] true if task is active
 * @param dayNum  : [OUT] repeating every 'dayNum' days
 * @param minNum  : [OUT] repeating every 'minNum' minutes
 * @return          0 = task found, status correct.
 *                  1 = task found, status incorrect.
 *                  2 = task found but manually changed.
 *                 -1 = task not found.
 *                 -2 = errors occurred.
 */
int getScheduleTask(bool* active, int* dayNum, int* minNum) {

    int ret = 0;
    HRESULT hr = S_OK;
    ITaskScheduler* pITS = NULL;
    ITask*          pITask;
    wstring taskName;

    // Init COM library & create instance for Task scheduler.
    if ((pITS = initScheduleInstance()) == NULL) {
        goto error;
    }

    // Task is associated with current user
    if (getScheduledTaskName(taskName)) {
        goto error;
    }

    //
    // Open the task (fails if task not existing).
    //
    hr = pITS->Activate(taskName.c_str(),
                        IID_ITask,
                        (IUnknown**) &pITask);
    pITS->Release();
    if (FAILED(hr)) {
        goto notExisting;
    }


    //
    // Check task status
    //
    DWORD pdwExitCode;
    hr = pITask->GetExitCode(&pdwExitCode);
    if ((hr & SCHED_S_TASK_HAS_NOT_RUN) || (hr != S_OK)) {
        setErrorF(getLastErrorCode(), DBG_SCHED_LAST_EXECUTION);
        LOG.debug(getLastErrorMsg());
        ret = 1;
    }


    //
    // Check the flags -> active/disabled
    //
    DWORD flag = -1, triggerType = -1;
    pITask->GetFlags(&flag);
    if (flag & TASK_FLAG_DISABLED) {
        *active = false;
    } else {
        *active = true;
    }


    //
    // Get the trigger interface.
    //
    ITaskTrigger *pITaskTrigger;
    hr = pITask->GetTrigger(0, &pITaskTrigger);
    if (FAILED(hr)) {
        char* msg = readSystemErrorMsg(hr);
        setErrorF(getLastErrorCode(), ERR_SCHED_GET_TRIGGER, hr, msg);
        delete [] msg;
        goto error;
    }

    TASK_TRIGGER pTrigger;
    ZeroMemory(&pTrigger, sizeof(TASK_TRIGGER));
    hr = pITaskTrigger->GetTrigger(&pTrigger);
    if (FAILED(hr)) {
        char* msg = readSystemErrorMsg(hr);
        setErrorF(getLastErrorCode(), ERR_SCHED_GET_TRIGGER2, hr, msg);
        delete [] msg;
        goto error;
    }

    //
    // Get values
    //
    int minToSet = pTrigger.MinutesInterval;
    if (minToSet == 1439) {
        minToSet = 1440;    // the value must normalized to get 1 day
    }

    *minNum = minToSet; // pTrigger.MinutesInterval;
    *dayNum = pTrigger.Type.Daily.DaysInterval;

    // Check if trigger correct
    triggerType = pTrigger.TriggerType;
    if (triggerType != TASK_TIME_TRIGGER_DAILY ||
        (pTrigger.MinutesDuration != 0
         && pTrigger.MinutesInterval != 0
         && pTrigger.MinutesDuration != 1440) ) {
        ret = 2;
    }

    // Release the ITask interface.
    pITaskTrigger->Release();
    pITask->Release();

    CoUninitialize();
    return ret;

notExisting:
    LOG.debug(DBG_SCHED_TASK_NOT_FOUND);
    *active = false;
    *minNum = 0;
    *dayNum = 0;
    CoUninitialize();
    return -1;

error:
    LOG.error(getLastErrorMsg());
    CoUninitialize();
    return -2;
}