<?php

/**
 * ---------------------------------------------------------------------
 *
 * GLPI - Gestionnaire Libre de Parc Informatique
 *
 * http://glpi-project.org
 *
 * @copyright 2015-2025 Teclib' and contributors.
 * @copyright 2003-2014 by the INDEPNET Development Team.
 * @licence   https://www.gnu.org/licenses/gpl-3.0.html
 *
 * ---------------------------------------------------------------------
 *
 * LICENSE
 *
 * This file is part of GLPI.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * ---------------------------------------------------------------------
 */

/**
 * LevelAgreement base Class for OLA & SLA
 * @since 9.2
 **/

abstract class LevelAgreement extends CommonDBChild
{
   // From CommonDBTM
    public $dohistory          = true;
    public static $rightname       = 'slm';

   // From CommonDBChild
    public static $itemtype = 'SLM';
    public static $items_id = 'slms_id';

    protected static $prefix            = '';
    protected static $prefixticket      = '';
    protected static $levelclass        = '';
    protected static $levelticketclass  = '';


    /**
     * Display a specific OLA or SLA warning.
     * Called into the above showForm() function
     *
     * @return void
     */
    abstract public function showFormWarning();

    /**
     * Return the text needed for a confirmation of adding level agreement to a ticket
     *
     * @return array of strings
     */
    abstract public function getAddConfirmation();

    /**
     * Get table fields
     *
     * @param integer $subtype of OLA/SLA, can be SLM::TTO or SLM::TTR
     *
     * @return array of 'date' and 'sla' field names
     */
    public static function getFieldNames($subtype)
    {

        $dateField = null;
        $laField  = null;

        switch ($subtype) {
            case SLM::TTO:
                $dateField = static::$prefixticket . 'time_to_own';
                $laField   = static::$prefix . 's_id_tto';
                break;

            case SLM::TTR:
                $dateField = static::$prefixticket . 'time_to_resolve';
                $laField   = static::$prefix . 's_id_ttr';
                break;
        }
        return [$dateField, $laField];
    }

    public static function getWaitingFieldName(): string
    {
        return static::$prefix . '_waiting_duration';
    }

    public function defineTabs($options = [])
    {

        $ong = [];
        $this->addDefaultFormTab($ong);
        $this->addStandardTab(static::$levelclass, $ong, $options);
        $this->addStandardTab('Rule', $ong, $options);
        $this->addStandardTab('Ticket', $ong, $options);

        return $ong;
    }

    /**
     * Define calendar of the ticket using the SLA/OLA when using this calendar as sla/ola-s calendar
     *
     * @param integer $calendars_id calendars_id of the ticket
     **/
    public function setTicketCalendar($calendars_id)
    {

        if ($this->fields['use_ticket_calendar']) {
            $this->fields['calendars_id'] = $calendars_id;
        }
    }

    public function post_getEmpty()
    {
        $this->fields['number_time'] = 4;
        $this->fields['definition_time'] = 'hour';
    }

    /**
     * Print the form
     *
     * @param $ID        integer  ID of the item
     * @param $options   array    of possible options:
     *     - target filename : where to go when done.
     *     - withtemplate boolean : template or basic item
     *
     *@return boolean item found
     **/
    public function showForm($ID, array $options = [])
    {
        $rowspan = 3;
        if ($ID > 0) {
            $rowspan = 5;
        }

       // Get SLM object
        $slm = new SLM();
        if (isset($options['parent'])) {
            $slm = $options['parent'];
        } else {
            $slm->getFromDB($this->fields['slms_id']);
        }

        if ($ID > 0) {
            $this->check($ID, READ);
        } else {
           // Create item
            $options[static::$items_id] = $slm->getField('id');

           //force itemtype of parent
            static::$itemtype = get_class($slm);

            $this->check(-1, CREATE, $options);
        }

        $this->showFormHeader($options);
        echo "<tr class='tab_bg_1'>";
        echo "<td>" . __('Name') . "</td>";
        echo "<td>";
        echo Html::input("name", ['value' => $this->fields["name"]]);
        echo "<td rowspan='" . $rowspan . "'>" . __('Comments') . "</td>";
        echo "<td rowspan='" . $rowspan . "'>
            <textarea class='form-control' rows='8' name='comment' >" . $this->fields["comment"] . "</textarea>";
        echo "</td></tr>";

        echo "<tr class='tab_bg_1'>";
        echo "<td>" . __('SLM') . "</td>";
        echo "<td>";
        echo $slm->getLink();
        echo "<input type='hidden' name='slms_id' value='" . $this->fields['slms_id'] . "'>";
        echo "</td></tr>";

        if ($ID > 0) {
            echo "<tr class='tab_bg_1'>";
            echo "<td>" . __('Last update') . "</td>";
            echo "<td>" . ($this->fields["date_mod"] ? Html::convDateTime($this->fields["date_mod"])
                                                : __('Never'));
            echo "</td></tr>";
        }

        echo "<tr class='tab_bg_1'><td>" . _n('Type', 'Types', 1) . "</td>";
        echo "<td>";
        self::getTypeDropdown(['value' => $this->fields["type"]]);
        echo "</td>";
        echo "</tr>";

        echo "<tr class='tab_bg_1'><td>" . __('Maximum time') . "</td>";
        echo "<td>";
        Dropdown::showNumber("number_time", ['value' => $this->fields["number_time"],
            'min'   => 0,
            'max'   => 1000
        ]);
        $possible_values = self::getDefinitionTimeValues();
        $rand = Dropdown::showFromArray(
            'definition_time',
            $possible_values,
            ['value'     => $this->fields["definition_time"],
                'on_change' => 'appearhideendofworking()'
            ]
        );
        echo "\n<script type='text/javascript' >\n";
        echo "function appearhideendofworking() {\n";
        echo "if ($('#dropdown_definition_time$rand option:selected').val() == 'day'
                  || $('#dropdown_definition_time$rand option:selected').val() == 'month') {
               $('#title_endworkingday').show();
               $('#dropdown_endworkingday').show();
            } else {
               $('#title_endworkingday').hide();
               $('#dropdown_endworkingday').hide();
            }";
        echo "}\n";
        echo "appearhideendofworking();\n";
        echo "</script>\n";

        echo "</td></tr>";

        echo "<tr class='tab_bg_1'>";
        echo "<td><div id='title_endworkingday'>" . __('End of working day') . "</div></td>";
        echo "<td><div id='dropdown_endworkingday'>";
        Dropdown::showYesNo("end_of_working_day", $this->fields["end_of_working_day"]);
        echo "</div></td>";

        echo "<td colspan='2'>";
        $this->showFormWarning();
        echo "</td>";
        echo "</tr>";

        $this->showFormButtons($options);

        return true;
    }

    /**
     * Get possibles keys and labels for the definition_time field
     *
     * @return array
     *
     * @since 10.0.0
     */
    public static function getDefinitionTimeValues(): array
    {
        return [
            'minute' => _n('Minute', 'Minutes', Session::getPluralNumber()),
            'hour'   => _n('Hour', 'Hours', Session::getPluralNumber()),
            'day'    => _n('Day', 'Days', Session::getPluralNumber()),
            'month'  => _n('Month', 'Months', Session::getPluralNumber())
        ];
    }

    /**
     * Get the matching label for a given key (definition_time field)
     *
     * @param string $value
     *
     * @return string
     *
     * @since 10.0.0
     */
    public static function getDefinitionTimeLabel(string $value): string
    {
        return self::getDefinitionTimeValues()[$value] ?? "";
    }


    /**
     * Get a level for a given action
     *
     * since 10.0
     *
     * @param mixed $nextaction
     *
     * @return false|LevelAgreementLevel
     */
    public function getLevelFromAction($nextaction)
    {
        if ($nextaction === false) {
            return false;
        }

        $pre  = static::$prefix;
        $nextlevel  = new static::$levelclass();
        if (!$nextlevel->getFromDB($nextaction->fields[$pre . 'levels_id'])) {
            return false;
        }

        return $nextlevel;
    }


    /**
     * Get then next levelagreement action for a given ticket and "LA" type
     *
     * since 10.0
     *
     * @param Ticket $ticket
     * @param int $type
     *
     * @return false|OlaLevel_Ticket|SlaLevel_Ticket
     */
    public function getNextActionForTicket(Ticket $ticket, int $type)
    {
        $nextaction = new static::$levelticketclass();
        if (!$nextaction->getFromDBForTicket($ticket->fields["id"], $type)) {
            return false;
        }

        return $nextaction;
    }


    /**
     * Print the HTML for a SLM
     *
     * @param SLM $slm Slm item
     */
    public static function showForSLM(SLM $slm)
    {
        /** @var array $CFG_GLPI */
        global $CFG_GLPI;

        if (!$slm->can($slm->fields['id'], READ)) {
            return false;
        }

        $instID   = $slm->fields['id'];
        $la       = new static();
        $calendar = new Calendar();
        $rand     = mt_rand();
        $canedit  = ($slm->canEdit($instID)
                   && Session::getCurrentInterface() == "central");

        if ($canedit) {
            echo "<div id='showLa$instID$rand'></div>\n";

            echo "<script type='text/javascript' >";
            echo "function viewAddLa$instID$rand() {";
            $params = ['type'                     => $la->getType(),
                'parenttype'               => $slm->getType(),
                $slm->getForeignKeyField() => $instID,
                'id'                       => -1
            ];
            Ajax::updateItemJsCode(
                "showLa$instID$rand",
                $CFG_GLPI["root_doc"] . "/ajax/viewsubitem.php",
                $params
            );
            echo "}";
            echo "</script>";
            echo "<div class='center firstbloc'>" .
               "<a class='btn btn-primary' href='javascript:viewAddLa$instID$rand();'>";
            echo __('Add a new item') . "</a></div>\n";
        }

       // list
        $laList = $la->find(['slms_id' => $instID]);
        Session::initNavigateListItems(
            __CLASS__,
            sprintf(
                __('%1$s = %2$s'),
                $slm::getTypeName(1),
                $slm->getName()
            )
        );
        echo "<div class='spaced'>";
        if (count($laList)) {
            if ($canedit) {
                Html::openMassiveActionsForm('mass' . __CLASS__ . $rand);
                $massiveactionparams = ['container' => 'mass' . __CLASS__ . $rand];
                Html::showMassiveActions($massiveactionparams);
            }

            echo "<table class='tab_cadre_fixehov'>";
            $header_begin  = "<tr>";
            $header_top    = '';
            $header_bottom = '';
            $header_end    = '';
            if ($canedit) {
                $header_top .= "<th width='10'>" . Html::getCheckAllAsCheckbox('mass' . __CLASS__ . $rand);
                $header_top .= "</th>";
                $header_bottom .= "<th width='10'>" . Html::getCheckAllAsCheckbox('mass' . __CLASS__ . $rand);
                $header_bottom .= "</th>";
            }
            $header_end .= "<th>" . __('Name') . "</th>";
            $header_end .= "<th>" . _n('Type', 'Types', 1) . "</th>";
            $header_end .= "<th>" . __('Maximum time') . "</th>";
            $header_end .= "<th>" . _n('Calendar', 'Calendars', 1) . "</th>";

            echo $header_begin . $header_top . $header_end;
            foreach ($laList as $val) {
                $edit = ($canedit ? "style='cursor:pointer' onClick=\"viewEditLa" .
                        $instID . $val["id"] . "$rand();\""
                        : '');
                echo "<script type='text/javascript' >";
                echo "function viewEditLa" . $instID . $val["id"] . "$rand() {";
                $params = ['type'                     => $la->getType(),
                    'parenttype'               => $slm->getType(),
                    $slm->getForeignKeyField() => $instID,
                    'id'                       => $val["id"]
                ];
                Ajax::updateItemJsCode(
                    "showLa$instID$rand",
                    $CFG_GLPI["root_doc"] . "/ajax/viewsubitem.php",
                    $params
                );
                echo "};";
                echo "</script>\n";

                echo "<tr class='tab_bg_1'>";
                echo "<td width='10' $edit>";
                if ($canedit) {
                     Html::showMassiveActionCheckBox($la->getType(), $val['id']);
                }
                echo "</td>";
                $la->getFromDB($val['id']);
                echo "<td $edit>" . $la->getLink() . "</td>";
                echo "<td $edit>" . $la->getSpecificValueToDisplay('type', $la->fields['type']) . "</td>";
                echo "<td $edit>";
                echo $la->getSpecificValueToDisplay(
                    'number_time',
                    ['number_time'     => $la->fields['number_time'],
                        'definition_time' => $la->fields['definition_time']
                    ]
                );
                echo "</td>";
                $link = '';
                if ($slm->fields['use_ticket_calendar']) {
                    $link = __('Calendar of the ticket');
                } else if (!$slm->fields['calendars_id']) {
                     $link =  __('24/7');
                } else if ($calendar->getFromDB($slm->fields['calendars_id'])) {
                    $link = $calendar->getLink();
                }
                echo "<td $edit>" . $link . "</td>";
                echo "</tr>";
            }
            echo $header_begin . $header_bottom . $header_end;
            echo "</table>";

            if ($canedit) {
                $massiveactionparams['ontop'] = false;
                Html::showMassiveActions($massiveactionparams);
                Html::closeForm();
            }
        } else {
            echo __('No item to display');
        }
        echo "</div>";
    }

    /**
     * Display a list of rule for the current sla/ola
     * @return void
     */
    public function showRulesList()
    {
        /** @var \DBmysql $DB */
        global $DB;

        $fk      = static::getFieldNames($this->fields['type'])[1];
        $rule    = new RuleTicket();
        $rand    = mt_rand();
        $canedit = self::canUpdate();

        $rules_id_list = iterator_to_array($DB->request([
            'SELECT'          => 'rules_id',
            'DISTINCT'        => true,
            'FROM'            => 'glpi_ruleactions',
            'WHERE'           => [
                'field' => $fk,
                'value' => $this->getID()
            ]
        ]));
        $nb = count($rules_id_list);

        echo "<div class='spaced'>";
        if (!$nb) {
            echo "<table class='tab_cadre_fixehov'>";
            echo "<tr><th>" . __('No item found') . "</th>";
            echo "</tr>\n";
            echo "</table>\n";
        } else {
            if ($canedit) {
                Html::openMassiveActionsForm('massRuleTicket' . $rand);
                $massiveactionparams
                 = ['num_displayed'    => min($_SESSION['glpilist_limit'], $nb),
                     'specific_actions' => ['update' => _x('button', 'Update'),
                         'purge'  => _x('button', 'Delete permanently')
                     ]
                 ];
                Html::showMassiveActions($massiveactionparams);
            }
            echo "<table class='tab_cadre_fixehov'>";
            $header_begin  = "<tr>";
            $header_top    = '';
            $header_bottom = '';
            $header_end    = '';
            if ($canedit) {
                $header_begin  .= "<th width='10'>";
                $header_top    .= Html::getCheckAllAsCheckbox('massRuleTicket' . $rand);
                $header_bottom .= Html::getCheckAllAsCheckbox('massRuleTicket' . $rand);
                $header_end    .= "</th>";
            }
            $header_end .= "<th>" . RuleTicket::getTypeName($nb) . "</th>";
            $header_end .= "<th>" . __('Active') . "</th>";
            $header_end .= "<th>" . __('Description') . "</th>";
            $header_end .= "</tr>\n";
            echo $header_begin . $header_top . $header_end;

            Session::initNavigateListItems(
                get_class($this),
                sprintf(
                    __('%1$s = %2$s'),
                    $rule->getTypeName(1),
                    $rule->getName()
                )
            );

            foreach ($rules_id_list as $data) {
                $rule->getFromDB($data['rules_id']);
                Session::addToNavigateListItems(get_class($this), $rule->fields["id"]);
                echo "<tr class='tab_bg_1'>";

                if ($canedit) {
                    echo "<td width='10'>";
                    Html::showMassiveActionCheckBox("RuleTicket", $rule->fields["id"]);
                    echo "</td>";
                    $ruleclassname = get_class($rule);
                    echo "<td><a href='" . $ruleclassname::getFormURLWithID($rule->fields["id"])
                       . "&amp;onglet=1'>" . $rule->fields["name"] . "</a></td>";
                } else {
                    echo "<td>" . $rule->fields["name"] . "</td>";
                }

                echo "<td>" . Dropdown::getYesNo($rule->fields["is_active"]) . "</td>";
                echo "<td>" . $rule->fields["description"] . "</td>";
                echo "</tr>\n";
            }
            echo $header_begin . $header_bottom . $header_end;
            echo "</table>\n";

            if ($canedit) {
                $massiveactionparams['ontop'] = false;
                Html::showMassiveActions($massiveactionparams);
                Html::closeForm();
            }
        }
        echo "</div>";
    }

    public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0)
    {

        if (!$withtemplate) {
            $nb = 0;
            switch ($item->getType()) {
                case 'SLM':
                    /** @var \SLM $item */
                    if ($_SESSION['glpishow_count_on_tabs']) {
                        $nb = countElementsInTable(
                            self::getTable(),
                            ['slms_id' => $item->getField('id')]
                        );
                    }
                    return self::createTabEntry(static::getTypeName($nb), $nb);
            }
        }
        return '';
    }


    public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0)
    {

        switch ($item->getType()) {
            case 'SLM':
                self::showForSLM($item);
                break;
        }
        return true;
    }


    /**
     * Get data by type and ticket
     *
     * @param $tickets_id
     * @param $type
     */
    public function getDataForTicket($tickets_id, $type)
    {
        /** @var \DBmysql $DB */
        global $DB;

        list($dateField, $field) = static::getFieldNames($type);

        $iterator = $DB->request([
            'SELECT'       => [static::getTable() . '.id'],
            'FROM'         => static::getTable(),
            'INNER JOIN'   => [
                'glpi_tickets' => [
                    'FKEY'   => [
                        static::getTable()   => 'id',
                        'glpi_tickets'       => $field
                    ]
                ]
            ],
            'WHERE'        => ['glpi_tickets.id' => $tickets_id],
            'LIMIT'        => 1
        ]);

        if (count($iterator)) {
            return $this->getFromIter($iterator);
        }
        return false;
    }


    public function rawSearchOptions()
    {
        $tab = [];

        $tab[] = [
            'id'                 => 'common',
            'name'               => __('Characteristics')
        ];

        $tab[] = [
            'id'                 => '1',
            'table'              => $this->getTable(),
            'field'              => 'name',
            'name'               => __('Name'),
            'datatype'           => 'itemlink',
            'massiveaction'      => false,
        ];

        $tab[] = [
            'id'                 => '2',
            'table'              => $this->getTable(),
            'field'              => 'id',
            'name'               => __('ID'),
            'massiveaction'      => false,
            'datatype'           => 'number'
        ];

        $tab[] = [
            'id'                 => '5',
            'table'              => $this->getTable(),
            'field'              => 'number_time',
            'name'               => _x('hour', 'Time'),
            'datatype'           => 'specific',
            'massiveaction'      => false,
            'nosearch'           => true,
            'additionalfields'   => ['definition_time']
        ];

        $tab[] = [
            'id'                 => '6',
            'table'              => $this->getTable(),
            'field'              => 'end_of_working_day',
            'name'               => __('End of working day'),
            'datatype'           => 'bool',
            'massiveaction'      => false
        ];

        $tab[] = [
            'id'                 => '7',
            'table'              => $this->getTable(),
            'field'              => 'type',
            'name'               => _n('Type', 'Types', 1),
            'datatype'           => 'specific'
        ];

        $tab[] = [
            'id'                 => '8',
            'table'              => 'glpi_slms',
            'field'              => 'name',
            'name'               => __('SLM'),
            'datatype'           => 'dropdown'
        ];

        $tab[] = [
            'id'                 => '16',
            'table'              => $this->getTable(),
            'field'              => 'comment',
            'name'               => __('Comments'),
            'datatype'           => 'text'
        ];

        return $tab;
    }


    /**
     * @param $field
     * @param $values
     * @param $options   array
     **/
    public static function getSpecificValueToDisplay($field, $values, array $options = [])
    {

        if (!is_array($values)) {
            $values = [$field => $values];
        }
        switch ($field) {
            case 'number_time':
                switch ($values['definition_time']) {
                    case 'minute':
                        return sprintf(_n('%d minute', '%d minutes', $values[$field]), $values[$field]);

                    case 'hour':
                        return sprintf(_n('%d hour', '%d hours', $values[$field]), $values[$field]);

                    case 'day':
                        return sprintf(_n('%d day', '%d days', $values[$field]), $values[$field]);
                }
                break;

            case 'type':
                return self::getOneTypeName($values[$field]);
        }
        return parent::getSpecificValueToDisplay($field, $values, $options);
    }


    /**
     * @param $field
     * @param $name            (default '')
     * @param $values          (default '')
     * @param $options   array
     *
     * @return string
     **/
    public static function getSpecificValueToSelect($field, $name = '', $values = '', array $options = [])
    {

        if (!is_array($values)) {
            $values = [$field => $values];
        }
        $options['display'] = false;
        switch ($field) {
            case 'type':
                $options['value'] = $values[$field];
                return self::getTypeDropdown($options);
        }
        return parent::getSpecificValueToSelect($field, $name, $values, $options);
    }


    /**
     * Get computed resolution time
     *
     * @return integer resolution time (default 0)
     **/
    public function getTime()
    {

        if (isset($this->fields['id'])) {
            if ($this->fields['definition_time'] == "minute") {
                return $this->fields['number_time'] * MINUTE_TIMESTAMP;
            }
            if ($this->fields['definition_time'] == "hour") {
                return $this->fields['number_time'] * HOUR_TIMESTAMP;
            }
            if ($this->fields['definition_time'] == "day") {
                return $this->fields['number_time'] * DAY_TIMESTAMP;
            }
            if ($this->fields['definition_time'] == "month") {
                return $this->fields['number_time'] * MONTH_TIMESTAMP;
            }
        }
        return 0;
    }


    /**
     * Get active time between to date time for the active calendar
     *
     * @param datetime $start begin
     * @param datetime $end end
     *
     * @return integer timestamp of delay
     **/
    public function getActiveTimeBetween($start, $end)
    {

        if ($end < $start) {
            return 0;
        }

        if (isset($this->fields['id'])) {
            $cal          = new Calendar();

           // Based on a calendar
            if ($this->fields['calendars_id'] > 0) {
                if ($cal->getFromDB($this->fields['calendars_id'])) {
                    return $cal->getActiveTimeBetween($start, $end);
                }
            } else { // No calendar
                $timestart = strtotime($start);
                $timeend   = strtotime($end);
                return ($timeend - $timestart);
            }
        }
        return 0;
    }


    /**
     * Get date for current agreement
     *
     * @param string  $start_date        datetime start date
     * @param integer $additional_delay  integer  additional delay to add or substract (for waiting time)
     *
     * @return string|null  due date time (NULL if sla/ola not exists)
     **/
    public function computeDate($start_date, $additional_delay = 0)
    {

        if (isset($this->fields['id'])) {
            $delay = $this->getTime();
           // Based on a calendar
            if ($this->fields['calendars_id'] > 0) {
                $cal          = new Calendar();
                $work_in_days = ($this->fields['definition_time'] == 'day' || $this->fields['definition_time'] == 'month');

                if ($cal->getFromDB($this->fields['calendars_id']) && $cal->hasAWorkingDay()) {
                    return $cal->computeEndDate(
                        $start_date,
                        $delay,
                        (int) $additional_delay,
                        $work_in_days,
                        $this->fields['end_of_working_day']
                    );
                }
            }

           // No calendar defined or invalid calendar
            if ($this->fields['number_time'] >= 0) {
                $starttime = strtotime($start_date);
                $endtime   = $starttime + $delay + (int) $additional_delay;
                return date('Y-m-d H:i:s', $endtime);
            }
        }

        return null;
    }

    /**
     * Should calculation on this LevelAgreement target date be done using
     * the "work_in_day" parameter set to true ?
     *
     * @return bool
     */
    public function shouldUseWorkInDayMode(): bool
    {
        return
            $this->fields['definition_time'] == 'day'
            || $this->fields['definition_time'] == 'month'
        ;
    }

    /**
     * Get execution date of a level
     *
     * @param string  $start_date        start date
     * @param integer $levels_id         sla/ola level id
     * @param integer $additional_delay  additional delay to add or substract (for waiting time)
     *
     * @return string|null  execution date time (NULL if ola/sla not exists)
     **/
    public function computeExecutionDate($start_date, $levels_id, $additional_delay = 0)
    {

        if (isset($this->fields['id'])) {
            $level = new static::$levelclass();
            $fk = getForeignKeyFieldForItemType(get_called_class());

            if ($level->getFromDB($levels_id)) { // level exists
                if ($level->fields[$fk] == $this->fields['id']) { // correct level
                    $delay        = $this->getTime();

                    // Based on a calendar
                    if ($this->fields['calendars_id'] > 0) {
                        $cal = new Calendar();
                        if ($cal->getFromDB($this->fields['calendars_id']) && $cal->hasAWorkingDay()) {
                            // Take SLA into account
                            $date_with_sla = $cal->computeEndDate(
                                $start_date,
                                $delay,
                                0,
                                $this->shouldUseWorkInDayMode(),
                                $this->fields['end_of_working_day']
                            );

                            // Take waiting duration time into account
                            $date_with_waiting_time = $cal->computeEndDate(
                                $date_with_sla,
                                $additional_delay,
                            );

                            // Take current SLA escalation level into account
                            $date_with_sla_and_escalation_level = $cal->computeEndDate(
                                $date_with_waiting_time,
                                $level->fields['execution_time'],
                                0,
                                $level->shouldUseWorkInDayMode(),
                            );

                            return $date_with_sla_and_escalation_level;
                        }
                    }
                    // No calendar defined or invalid calendar
                    $delay    += $additional_delay + $level->fields['execution_time'];
                    $starttime = strtotime($start_date);
                    $endtime   = $starttime + $delay;
                    return date('Y-m-d H:i:s', $endtime);
                }
            }
        }
        return null;
    }


    /**
     * Get types
     *
     * @return array array of types
     **/
    public static function getTypes()
    {
        return [SLM::TTO => __('Time to own'),
            SLM::TTR => __('Time to resolve')
        ];
    }


    /**
     * Get types name
     *
     * @param  integer $type
     * @return string  name
     **/
    public static function getOneTypeName($type)
    {

        $types = self::getTypes();
        $name  = null;
        if (isset($types[$type])) {
            $name = $types[$type];
        }
        return $name;
    }


    /**
     * Get SLA types dropdown
     *
     * @param array $options
     *
     * @return string
     */
    public static function getTypeDropdown($options)
    {

        $params = ['name'  => 'type'];

        foreach ($options as $key => $val) {
            $params[$key] = $val;
        }

        return Dropdown::showFromArray($params['name'], self::getTypes(), $options);
    }


    public function prepareInputForAdd($input)
    {

        if (
            $input['definition_time'] != 'day'
            && $input['definition_time'] != 'month'
        ) {
            $input['end_of_working_day'] = 0;
        }

        // Copy calendar settings from SLM
        $slm = new SLM();
        if (array_key_exists('slms_id', $input) && $slm->getFromDB($input['slms_id'])) {
            $input['use_ticket_calendar'] = $slm->fields['use_ticket_calendar'];
            $input['calendars_id'] = $slm->fields['calendars_id'];
        }

        return $input;
    }


    public function prepareInputForUpdate($input)
    {

        if (
            isset($input['definition_time']) && ($input['definition_time'] != 'day'
            && $input['definition_time'] != 'month')
        ) {
            $input['end_of_working_day'] = 0;
        }

        // Copy calendar settings from SLM
        $slm = new SLM();
        if (
            array_key_exists('slms_id', $input)
            && $input['slms_id'] != $this->fields['slms_id']
            && $slm->getFromDB($input['slms_id'])
        ) {
            $input['use_ticket_calendar'] = $slm->fields['use_ticket_calendar'];
            $input['calendars_id'] = $slm->fields['calendars_id'];
        }

        return $input;
    }


    /**
     * Add a level to do for a ticket
     *
     * @param Ticket  $ticket Ticket object
     * @param integer $levels_id SlaLevel or OlaLevel ID
     *
     * @return void
     **/
    public function addLevelToDo(Ticket $ticket, $levels_id = 0)
    {

        $pre = static::$prefix;

        if (!$levels_id && isset($ticket->fields[$pre . 'levels_id_ttr'])) {
            $levels_id = $ticket->fields[$pre . "levels_id_ttr"];
        }

        if ($levels_id) {
            $toadd = [];

            // Compute start date
            if ($pre === "ola") {
                // OLA have their own start date which is set when the OLA is added to the ticket
                if (
                    (int) $this->fields['type'] === SLM::TTO
                    && $ticket->fields['ola_tto_begin_date'] !== null
                ) {
                    $date_field = "ola_tto_begin_date";
                } elseif (
                    (int) $this->fields['type'] === SLM::TTR
                    && $ticket->fields['ola_ttr_begin_date'] !== null
                ) {
                    $date_field = "ola_ttr_begin_date";
                } else {
                    // Fall back to default date in case the specific date fields
                    // are not set (which may be the case for tickets created
                    // before their addition)
                    $date_field = 'date';
                }
            } else {
                // SLA are based on the ticket opening date
                $date_field = 'date';
            }

            $date = $this->computeExecutionDate(
                $ticket->fields[$date_field],
                $levels_id,
                $ticket->fields[$pre . '_waiting_duration']
            );
            if ($date != null) {
                $toadd['date']           = $date;
                $toadd[$pre . 'levels_id'] = $levels_id;
                $toadd['tickets_id']     = $ticket->fields["id"];
                $levelticket             = new static::$levelticketclass();
                $levelticket->add($toadd);
            }
        }
    }


    /**
     * remove a level to do for a ticket
     *
     * @param $ticket Ticket object
     *
     * @return void
     **/
    public static function deleteLevelsToDo(Ticket $ticket)
    {
        /** @var \DBmysql $DB */
        global $DB;

        $ticketfield = static::$prefix . "levels_id_ttr";

        if ($ticket->fields[$ticketfield] > 0) {
            $levelticket = new static::$levelticketclass();
            $iterator = $DB->request([
                'SELECT' => 'id',
                'FROM'   => $levelticket::getTable(),
                'WHERE'  => ['tickets_id' => $ticket->fields['id']]
            ]);

            foreach ($iterator as $data) {
                 $levelticket->delete(['id' => $data['id']]);
            }
        }
    }


    public function cleanDBonPurge()
    {
        /** @var \DBmysql $DB */
        global $DB;

       // Clean levels
        $classname = get_called_class();
        $fk        = getForeignKeyFieldForItemType($classname);
        $level     = new static::$levelclass();
        $level->deleteByCriteria([$fk => $this->getID()]);

       // Update tickets : clean SLA/OLA
        list($dateField, $laField) = static::getFieldNames($this->fields['type']);
        $iterator =  $DB->request([
            'SELECT' => 'id',
            'FROM'   => 'glpi_tickets',
            'WHERE'  => [$laField => $this->fields['id']]
        ]);

        if (count($iterator)) {
            $ticket = new Ticket();
            foreach ($iterator as $data) {
                $ticket->deleteLevelAgreement($classname, $data['id'], $this->fields['type']);
            }
        }

        Rule::cleanForItemAction($this);
    }

    public function post_clone($source, $history)
    {
        // Clone levels
        $classname = get_called_class();
        $fk        = getForeignKeyFieldForItemType($classname);
        $level     = new static::$levelclass();
        foreach ($level->find([$fk => $source->getID()]) as $data) {
            $level->getFromDB($data['id']);
            $level->clone([$fk => $this->getID()]);
        }
    }

    /**
     * Getter for the protected $levelclass static property
     *
     * @return string
     */
    public function getLevelClass(): string
    {
        return static::$levelclass;
    }

    /**
     * Getter for the protected $levelticketclass static property
     *
     * @return string
     */
    public function getLevelTicketClass(): string
    {
        return static::$levelticketclass;
    }

    /**
     * Remove level of previously assigned level agreements for a given ticket
     *
     * @param int $tickets_id
     *
     * @return void
     */
    public function clearInvalidLevels(int $tickets_id): void
    {
        // CLear levels of others LA of the same type
        // e.g. if a new LA TTR was assigned, clear levels from others (= previous) LA TTR
        $level_ticket_class = $this->getLevelTicketClass();
        $level_class = $this->getLevelClass();
        $levels = (new $level_ticket_class())->find([
            'tickets_id' => $tickets_id,
            [$level_class::getForeignKeyField() => ['!=', $this->getID()]],
            [
                $level_class::getForeignKeyField() => new QuerySubQuery([
                    'SELECT' => 'id',
                    'FROM' => $level_class::getTable(),
                    'WHERE' => [
                        static::getForeignKeyField() => new QuerySubQuery([
                            'SELECT' => 'id',
                            'FROM' => static::getTable(),
                            'WHERE' => ['type' => $this->fields['type']],
                        ])
                    ]
                ]),
            ]
        ]);

        // Delete invalid levels
        foreach ($levels as $level) {
            $em = new $level_ticket_class();
            $em->delete(['id' => $level['id']]);
        }
    }
}
