<?php
/**
 * i-doit - Documentation and CMDB solution for IT environments
 *
 * This file is part of the i-doit framework. Modify at your own risk.
 *
 * Please visit http://www.i-doit.com/license for a full copyright and license information.
 *
 * @version     1.10
 * @package     i-doit
 * @author      synetics GmbH
 * @copyright   synetics GmbH
 * @url         http://www.i-doit.com
 * @license     http://www.i-doit.com/license
 */

namespace idoit\Console\Command\Workflow;

use idoit\Console\Command\AbstractCommand;
use isys_cmdb_dao_category_s_person_group_members;
use isys_contact_dao_reference;
use isys_convert;
use isys_event_task_information;
use isys_workflow;
use isys_workflow_dao;
use isys_workflow_dao_action;
use isys_workflow_dao_dynamic;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class HandleWorkflowsCommand extends AbstractCommand
{
    const NAME                  = 'workflows-process';
    const DEFAULT_WORKFLOW_TYPE = 'checklist';

    /**
     * Workflow-DAO
     *
     * @var  isys_workflow_dao
     */
    private $m_dao;

    /**
     * Workflow-Action-DAO
     *
     * @var  isys_workflow_dao_action
     */
    private $m_dao_action;

    /**
     * Contact dao
     *
     * @var  isys_contact_dao_reference
     */
    private $m_dao_contact_ref;

    /**
     * Contact dao
     *
     * @var  isys_cmdb_dao_category_s_person_group_members
     */
    private $m_dao_person_group;

    /**
     * @var \DateTime
     */
    private $currentDate;

    /**
     * Workflow instance
     *
     * @var  isys_workflow
     */
    private $m_workflow;

    /**
     * @var OutputInterface
     */
    private $output;

    /**
     * @var string[]
     */
    private $workflowTypes = [self::DEFAULT_WORKFLOW_TYPE];

    /**
     * Get name for command
     *
     * @return string
     */
    public function getCommandName()
    {
        return self::NAME;
    }

    /**
     * Get description for command
     *
     * @return string
     */
    public function getCommandDescription()
    {
        return 'Process all workflows, send e-mails and create new tasks from checklists';
    }

    /**
     * Retrieve Command InputDefinition
     *
     * @return InputDefinition
     */
    public function getCommandDefinition()
    {
        $definition = new InputDefinition();
        $definition->addOption(new InputOption(
            'types',
            't',
            InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
            'Workflow types',
            []
        ));

        return $definition;
    }

    /**
     * Checks if a command can have a config file via --config
     *
     * @return bool
     */
    public function isConfigurable()
    {
        return true;
    }

    /**
     * Returns an array of command usages
     *
     * @return string[]
     */
    public function getCommandUsages()
    {
        return [
            '-u {user} -p {password} -i {tenantId} --types=task',
            '-u {user} -p {password} -i {tenantId} --types=checklist',
            '-u {user} -p {password} -i {tenantId} --types=task --types=checklist'
        ];
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->output = $output;
        $this->currentDate = new \DateTime();

        if (count($input->getOption('types')) !== 0) {
            $this->workflowTypes = $input->getOption('types');
        }

        // Get daos, because now we are logged in.
        $database = $this->container->database;
        $this->m_dao              = new isys_workflow_dao($database);
        $this->m_dao_action       = new isys_workflow_dao_action($database);
        $this->m_dao_contact_ref  = new isys_contact_dao_reference($database);
        $this->m_dao_person_group = new isys_cmdb_dao_category_s_person_group_members($database);

        // Get Workflow instance.
        $this->m_workflow = new isys_workflow();

        try
        {
            $this->parseWorkflows();
        } catch (\Exception $exception) {
            $this->output->writeln('<error>' . $exception->getMessage() . '</error>');
        }
    }

    private function parseWorkflows()
    {
        $g_comp_template_language_manager = new \isys_component_template_language_manager($this->container->application->language);

        $this->output->writeln("Retrieving active workflows");

        $l_filter = "AND ((" . "date_format(start_date.isys_workflow_action_parameter__datetime, '%Y%m%d') <= date_format(NOW(), '%Y%m%d')" . ") " . "OR (" .
            "date_format(end_date.isys_workflow_action_parameter__datetime, '%Y%m%d') >= date_format(NOW(), '%Y%m%d')" . ")) " . "AND (isys_workflow__occurrence > 0)";

        $l_actions = $this->m_dao->get_workflows(
            null,
            null,
            C__WORKFLOW_TYPE__CHECKLIST,
            C__WORKFLOW__ACTION__TYPE__NEW,
            $l_filter,
            null,
            null,
            null,
            "isys_workflow__datetime DESC"
        );

        $l_workflow_task = in_array('task', $this->workflowTypes);
        $l_workflow_checklist = in_array('checklist', $this->workflowTypes);

        // Check Checklists
        if ($l_workflow_checklist)
        {
            if ($l_actions->num_rows() > 0)
            {
                $this->output->writeln($l_actions->num_rows() . " Checklists found.");
                while ($l_row = $l_actions->get_row())
                {
                    $this->output->writeln("Parsing workflow: " . $l_row["isys_workflow__title"] . " ");

                    $l_id         = 0;
                    $l_start_date = date("Ymd", strtotime($l_row["startdate"]));

                    $l_today = date("Ymd");

                    switch ($l_row["isys_workflow__occurrence"])
                    {
                        case C__TASK__OCCURRENCE__HOURLY:
                            $this->output->writeln("<info>- Hourly tasks are not implemented yet.</info>");
                            break;
                        case C__TASK__OCCURRENCE__DAILY:

                            /* Checking exception */
                            if (!(1 << date("w") & $l_row["isys_workflow__exception"]))
                            {
                                $l_id = $this->addWorkflow($l_row["isys_workflow__id"]);
                            }
                            else
                            {
                                $this->output->writeln("- Task omitted because of an exception rule. (" . date("D") . ")");
                            }

                            break;
                        case C__TASK__OCCURRENCE__WEEKLY:
                        case C__TASK__OCCURRENCE__EVERY_TWO_WEEKS:
                        case C__TASK__OCCURRENCE__MONTHLY:
                        case C__TASK__OCCURRENCE__YEARLY:

                            if ($this->check($l_start_date, $l_today, $l_row["isys_workflow__occurrence"]))
                            {
                                $l_id = $this->addWorkflow($l_row["isys_workflow__id"]);
                            }
                            else
                            {
                                $this->output->writeln("- Task omitted because occurrence setting did not match the current date.");
                            } // if

                            break;
                    } // switch

                    if ($l_id > 0)
                    {
                        $this->output->writeln("Task Added. <info>({$l_id})</info>");
                    } // if
                } // while
            }
            else
            {
                $this->output->writeln("No circular workflows found");
            } // if
        }
        else
        {
            $this->output->writeln("Checklists ignored.");
        } // if

        // Check Tasks
        if ($l_workflow_task)
        {
            $l_sql = 'SELECT w0.*, w1.*, w2.*, w3.*,
				a1.isys_workflow_action_parameter__datetime AS end_datetime,
				a2.isys_workflow_action_parameter__int AS notice_days,
				a3.isys_workflow_action_parameter__datetime AS start_datetime,
				a4.isys_workflow_action_parameter__text AS description ' .

                'FROM isys_workflow_action AS w0 ' .

                'INNER JOIN isys_workflow_action_parameter a3 ON a3.isys_workflow_action_parameter__isys_workflow_action__id = w0.isys_workflow_action__id ' .
                'AND isys_workflow_action_parameter__key LIKE \'%start_date\' ' .
                'INNER JOIN isys_workflow_action_parameter a4 ON a4.isys_workflow_action_parameter__isys_workflow_action__id = w0.isys_workflow_action__id ' .
                'AND a4.isys_workflow_action_parameter__key LIKE \'%description\' ' .
                'INNER JOIN isys_workflow_action_parameter a1 ON a1.isys_workflow_action_parameter__isys_workflow_action__id = w0.isys_workflow_action__id ' .
                'AND a1.isys_workflow_action_parameter__key LIKE \'%end_date\' ' .
                'INNER JOIN isys_workflow_action_parameter a2 ON a2.isys_workflow_action_parameter__isys_workflow_action__id = w0.isys_workflow_action__id ' .
                'AND a2.isys_workflow_action_parameter__int > 0 ' .

                'INNER JOIN isys_workflow_2_isys_workflow_action w1 ON w1.isys_workflow_2_isys_workflow_action__isys_workflow_action__id = w0.isys_workflow_action__id ' .
                'INNER JOIN isys_workflow_action_type w2 ON w2.isys_workflow_action_type__id = w0.isys_workflow_action__isys_workflow_action_type__id ' .
                'INNER JOIN isys_workflow w3 ON w3.isys_workflow__id = w1.isys_workflow_2_isys_workflow_action__isys_workflow__id ' .
                'LEFT OUTER JOIN isys_workflow_category ON isys_workflow_category__id = w3.isys_workflow__isys_workflow_category__id ' . 'WHERE TRUE  ' .
                'AND (w3.isys_workflow__isys_workflow_type__id = ' . $this->m_dao->convert_sql_id(
                    C__WORKFLOW_TYPE__TASK
                ) . ') ' . 'AND (w3.isys_workflow__status = ' . $this->m_dao->convert_sql_int(
                    C__RECORD_STATUS__NORMAL
                ) . ') ' . 'AND (w2.isys_workflow_action_type__id = ' . $this->m_dao->convert_sql_id(C__WORKFLOW__ACTION__TYPE__NEW) . ') ' . 'GROUP BY w3.isys_workflow__id';

            $l_res   = $this->m_dao->retrieve($l_sql);
            $l_count = $l_res->num_rows();
            if ($l_count > 0)
            {

                $l_obj_type_id = array_pop(
                    $this->m_dao->retrieve(
                        'SELECT isys_obj_type__id FROM isys_obj_type WHERE isys_obj_type__const = ' . $this->m_dao->convert_sql_text('C__OBJTYPE__PERSON')
                    )->get_row()
                );

                $this->output->writeln($l_count . " Tasks found.");

                $l_expired = 0;
                $l_active  = 0;
                $l_send    = 0;

                while ($l_row = $l_res->get_row())
                {
                    $this->m_dao_contact_ref->load($l_row['isys_workflow_action__isys_contact__id']);

                    $l_contact_initiator_id       = array_pop(array_keys($this->m_dao_contact_ref->get_data_item_array()));
                    $l_contact_initiator_data_arr = $this->m_dao_contact_ref->get_data_item_data($l_contact_initiator_id);
                    $l_contact_initiator_data     = method_exists($l_contact_initiator_data_arr['dao'], 'get_row') ? $l_contact_initiator_data_arr['dao']->get_row() : [];
                    $l_initiator                  = $l_contact_initiator_data['isys_cats_person_list__first_name'] .
                                                    ' ' .
                                                    $l_contact_initiator_data['isys_cats_person_list__last_name'];

                    $l_end_date     = strtotime($l_row['end_datetime']);
                    $l_notice_days  = $l_row['notice_days'] * isys_convert::DAY;
                    $l_current_date = strtotime(date('Y-m-d', time()));

                    if (($l_current_date + $l_notice_days) == $l_end_date)
                    {
                        $l_sql = 'SELECT action_new.isys_workflow_action__isys_contact__id AS contact_id FROM isys_workflow_action AS action_new
							INNER JOIN isys_workflow_2_isys_workflow_action ON isys_workflow_2_isys_workflow_action__isys_workflow_action__id = action_new.isys_workflow_action__id
							INNER JOIN isys_workflow ON isys_workflow__id = isys_workflow_2_isys_workflow_action__isys_workflow__id
							INNER JOIN isys_workflow_action_type ON isys_workflow_action_type__id = action_new.isys_workflow_action__isys_workflow_action_type__id
							WHERE TRUE  AND (isys_workflow__id = ' . $this->m_dao->convert_sql_id($l_row['isys_workflow__id']) . ')
							AND isys_workflow_action_type__id = ' . C__WORKFLOW__ACTION__TYPE__ASSIGN;

                        $l_res2 = $this->m_dao->retrieve($l_sql);

                        if ($l_res2->num_rows() > 0)
                        {
                            $l_data       = $l_res2->get_row();
                            $l_contact_id = $l_data['contact_id'];

                            $this->m_dao_contact_ref->load($l_contact_id);
                            $l_contacts = $this->m_dao_contact_ref->get_data_item_array();

                            foreach (array_keys($l_contacts) AS $l_obj_id)
                            {
                                $l_cont_obj_type_id = array_pop(
                                    $this->m_dao->retrieve(
                                        'SELECT isys_obj__isys_obj_type__id FROM isys_obj WHERE isys_obj__id = ' . $this->m_dao->convert_sql_id($l_obj_id)
                                    )->get_row()
                                );

                                if ($l_cont_obj_type_id == $l_obj_type_id)
                                {
                                    // Object type person
                                    $l_res_cont = $this->m_dao_contact_ref->get_data_item_info($l_obj_id, $l_cont_obj_type_id);

                                    while ($l_row_cont = $l_res_cont->get_row())
                                    {
                                        // Send task information to persons
                                        new isys_event_task_information(
                                            $l_row['isys_workflow__id'],
                                            $l_row_cont['isys_cats_person_list__mail_address'],
                                            null,
                                            $l_initiator,
                                            $l_row['notice_days'] . ' ' . $g_comp_template_language_manager->get('LC__TASK__DAY'),
                                            $l_row['start_datetime'],
                                            $l_row['end_datetime'],
                                            $l_row['description']
                                        );
                                    } // while
                                }
                                else
                                {
                                    // Object type person group
                                    $l_res_cont = $this->m_dao_contact_ref->get_data_item_info($l_obj_id, $l_cont_obj_type_id);

                                    while ($l_row_cont = $l_res_cont->get_row())
                                    {
                                        $l_res_cont_members = $this->m_dao_person_group->get_data(null, $l_row_cont['isys_obj__id']);

                                        while ($l_row_cont_members = $l_res_cont_members->get_row())
                                        {
                                            // Send task information to persons
                                            new isys_event_task_information(
                                                $l_row['isys_workflow__id'],
                                                $l_row_cont_members['isys_cats_person_list__mail_address'],
                                                null,
                                                $l_initiator,
                                                $l_row['notice_days'] . ' ' . $g_comp_template_language_manager->get('LC__TASK__DAY'),
                                                $l_row['start_datetime'],
                                                $l_row['end_datetime'],
                                                $l_row['description']
                                            );
                                        } // while
                                    } // while
                                } // if
                            } // foreach
                        } // if

                        $l_active++;
                        $l_send++;
                    }
                    else
                    {
                        if (($l_current_date) > $l_end_date)
                        {
                            $l_expired++;
                        }
                        elseif (($l_current_date) <= $l_end_date)
                        {
                            $l_active++;
                        } // if
                    } // if
                } // while

                $this->output->writeln($l_expired . " Task(s) are expired.");
                $this->output->writeln($l_active . " Task(s) are still active.");
                $this->output->writeln($l_send . " Notice(s) were send.");
            }
            else
            {
                $this->output->writeln("No Tasks found");
            } // if
        }
        else
        {
            $this->output->writeln("Tasks ignored");
        } // if
    } // function

    /**
     * @param   integer $workflowId
     *
     * @return  integer
     */
    private function addWorkflow($workflowId)
    {
        $l_dao_dynamic = new isys_workflow_dao_dynamic($this->container->database);

        $this->m_workflow->load($workflowId);
        $l_workflow_data = $this->m_workflow->get_data();

        // Get a DB-Conform date.
        $l_current_startdate = date("Y-m-d");

        $l_workflow__id = $l_dao_dynamic->create_task($workflowId, $this->m_workflow, $l_workflow_data, $l_current_startdate);

        if ($l_workflow__id == -1)
        {
            $this->output->writeln("- No workflow data found.");
        } // if

        if (empty($l_workflow__id))
        {
            $this->output->writeln("- Task for {$l_current_startdate} already existing.");
        } // if

        return $l_workflow__id;
    } // function

    /**
     * Match given date.
     *
     * @param   string  $p_start_date
     * @param   string  $p_today
     * @param   integer $p_occurrence
     *
     * @return  boolean
     */
    private function check($p_start_date, $p_today, $p_occurrence)
    {
        // Get a date difference.
        $l_date_diff = abs(strtotime($p_today) - strtotime($p_start_date)) / isys_convert::DAY;

        $l_month = date("m", strtotime($p_start_date));
        $l_day   = date("d", strtotime($p_start_date));

        switch ($p_occurrence)
        {
            case C__TASK__OCCURRENCE__WEEKLY:
                if (($l_date_diff % 7) == 0)
                {
                    return true;
                } // if
                break;

            case C__TASK__OCCURRENCE__EVERY_TWO_WEEKS:
                if (($l_date_diff % 14) == 0)
                {
                    return true;
                } // if
                break;

            case C__TASK__OCCURRENCE__MONTHLY:

                $l_days_in_month = date('t', strtotime($this->currentDate->format('Y') . '-' . $this->currentDate->format('m') . '-01'));
                if (checkdate($this->currentDate->format('m'), $l_day, $this->currentDate->format('Y')))
                {
                    if ($this->currentDate->format('d') == $l_day)
                    {
                        return true;
                    } // if
                }
                else
                {
                    if ($l_days_in_month <= $l_day && $this->currentDate->format('d') == $l_days_in_month)
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    } // if
                } // if
                break;

            case C__TASK__OCCURRENCE__YEARLY:

                if (checkdate($this->currentDate->format('m'), $l_day, $this->currentDate->format('Y')))
                {
                    if ($l_month == $this->currentDate->format('m') && $l_day == $this->currentDate->format('d'))
                    {
                        return true;
                    } // if
                }
                else
                {
                    if ($this->currentDate->format('d') < $l_day)
                    {
                        return true;
                    }
                } // if
                break;
        } // switch

        return false;
    } // function
}
