<?php

/**
 * ---------------------------------------------------------------------
 *
 * GLPI - Gestionnaire Libre de Parc Informatique
 *
 * http://glpi-project.org
 *
 * @copyright 2015-2023 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/>.
 *
 * ---------------------------------------------------------------------
 */

class Item_Rack extends CommonDBRelation
{
    public static $itemtype_1 = 'Rack';
    public static $items_id_1 = 'racks_id';
    public static $itemtype_2 = 'itemtype';
    public static $items_id_2 = 'items_id';
    public static $checkItem_2_Rights = self::DONT_CHECK_ITEM_RIGHTS;
    public static $mustBeAttached_1 = false; // FIXME It make no sense for a rack item to not be attached to a Rack.
    public static $mustBeAttached_2 = false; // FIXME It make no sense for a rack item to not be attached to an Item.

    public static function getTypeName($nb = 0)
    {
        return _n('Item', 'Item', $nb);
    }


    public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0)
    {
        $nb = 0;
        switch ($item->getType()) {
            default:
                if ($_SESSION['glpishow_count_on_tabs']) {
                    $nb = countElementsInTable(
                        self::getTable(),
                        ['racks_id'  => $item->getID()]
                    );
                    $nb += countElementsInTable(
                        PDU_Rack::getTable(),
                        ['racks_id'  => $item->getID()]
                    );
                }
                return self::createTabEntry(self::getTypeName(Session::getPluralNumber()), $nb);
        }
        return '';
    }

    public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0)
    {
        self::showItems($item);
        return true;
    }

    public function getForbiddenStandardMassiveAction()
    {
        $forbidden   = parent::getForbiddenStandardMassiveAction();
        $forbidden[] = 'MassiveAction:update';
        $forbidden[] = 'CommonDBConnexity:affect';
        $forbidden[] = 'CommonDBConnexity:unaffect';

        return $forbidden;
    }

    public static function processMassiveActionsForOneItemtype(
        MassiveAction $ma,
        CommonDBTM $item,
        array $ids
    ) {
        switch ($ma->getAction()) {
            case 'delete':
                $input = $ma->getInput();
                $item_rack = new Item_Rack();
                foreach ($ids as $id) {
                    if ($item->can($id, UPDATE, $input)) {
                        $relation_criteria = [
                            'itemtype' => $item->getType(),
                            'items_id' => $item->getID()
                        ];
                        if (countElementsInTable(Item_Rack::getTable(), $relation_criteria) > 0) {
                            if ($item_rack->deleteByCriteria($relation_criteria)) {
                                $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK);
                            } else {
                                $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO);
                                $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION));
                            }
                        }
                    } else {
                        $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_NORIGHT);
                        $ma->addMessage($item->getErrorMessage(ERROR_RIGHT));
                    }
                }
                return;
        }
        parent::processMassiveActionsForOneItemtype($ma, $item, $ids);
    }

    /**
     * Print racks items
     * @param  Rack   $rack the current rack instance
     * @return void
     */
    public static function showItems(Rack $rack)
    {
        global $DB, $CFG_GLPI;

        $ID = $rack->getID();
        $rand = mt_rand();

        if (
            !$rack->getFromDB($ID)
            || !$rack->can($ID, READ)
        ) {
            return false;
        }
        $canedit = $rack->canEdit($ID);

        $items = $DB->request([
            'FROM'   => self::getTable(),
            'WHERE'  => [
                'racks_id' => $rack->getID()
            ],
            'ORDER' => 'position DESC'
        ]);
        $link = new self();

        if ($canedit) {
            Session::initNavigateListItems(
                self::getType(),
                //TRANS : %1$s is the itemtype name,
                //        %2$s is the name of the item (used for headings of a list)
                sprintf(
                    __('%1$s = %2$s'),
                    $rack->getTypeName(1),
                    $rack->getName()
                )
            );
        }

        echo "<div id='switchview'>";
        echo "<i id='sviewlist' class='pointer ti ti-list' title='" . __('View as list') . "'></i>";
        echo "<i id='sviewgraph' class='pointer ti ti-server selected' title='" . __('View graphical representation') . "'></i>";
        echo "</div>";

        $items = iterator_to_array($items);
        echo "<div id='viewlist'>";

        echo "<h2>" . __("Racked items") . "</h2>";
        if (!count($items)) {
            echo "<table class='tab_cadre_fixe'><tr><th>" . __('No item found') . "</th></tr>";
            echo "</table>";
        } else {
            if ($canedit) {
                Html::openMassiveActionsForm('mass' . __CLASS__ . $rand);
                $massiveactionparams = [
                    'num_displayed'   => min($_SESSION['glpilist_limit'], count($items)),
                    'container'       => 'mass' . __CLASS__ . $rand
                ];
                Html::showMassiveActions($massiveactionparams);
            }

            echo "<table class='tab_cadre_fixehov'>";
            $header = "<tr>";
            if ($canedit) {
                $header .= "<th width='10'>";
                $header .= Html::getCheckAllAsCheckbox('mass' . __CLASS__ . $rand);
                $header .= "</th>";
            }
            $header .= "<th>" . _n('Item', 'Items', 1) . "</th>";
            $header .= "<th>" . __('Position') . "</th>";
            $header .= "<th>" . __('Orientation') . "</th>";
            $header .= "</tr>";

            echo $header;
            foreach ($items as $row) {
                $item = new $row['itemtype']();
                $item->getFromDB($row['items_id']);
                echo "<tr lass='tab_bg_1'>";
                if ($canedit) {
                    echo "<td>";
                    Html::showMassiveActionCheckBox(__CLASS__, $row["id"]);
                    echo "</td>";
                }
                echo "<td>" . $item->getLink() . "</td>";
                echo "<td>{$row['position']}</td>";
                $txt_orientation = $row['orientation'] == Rack::FRONT ? __('Front') : __('Rear');
                echo "<td>$txt_orientation</td>";
                echo "</tr>";
            }
            echo $header;
            echo "</table>";

            if ($canedit && count($items)) {
                $massiveactionparams['ontop'] = false;
                Html::showMassiveActions($massiveactionparams);
            }
            if ($canedit) {
                Html::closeForm();
            }
        }

        PDU_Rack::showListForRack($rack);

        echo "</div>";
        echo "<div id='viewgraph'>";

        $data = [];
       //all rows; empty
        for ($i = (int)$rack->fields['number_units']; $i > 0; --$i) {
            $data[Rack::FRONT][$i] = false;
            $data[Rack::REAR][$i] = false;
        }

       //fill rows
        $outbound = [];
        foreach ($items as $row) {
            $rel  = new self();
            $rel->getFromDB($row['id']);
            $item = new $row['itemtype']();
            if (!$item->getFromDB($row['items_id'])) {
                continue;
            }

            $position = $row['position'];

            $gs_item = [
                'id'        => $row['id'],
                'name'      => $item->getName(),
                'x'         => $row['hpos'] >= 2 ? 1 : 0,
                'y'         => $rack->fields['number_units'] - $row['position'],
                'height'    => 1,
                'width'     => 2,
                'bgcolor'   => $row['bgcolor'],
                'picture_f' => null,
                'picture_r' => null,
                'url'       => $item->getLinkURL(),
                'rel_url'   => $rel->getLinkURL(),
                'rear'      => false,
                'half_rack' => false,
                'reserved'  => (bool) $row['is_reserved'],
            ];

            $model_class = $item->getType() . 'Model';
            $modelsfield = strtolower($item->getType()) . 'models_id';
            $model = new $model_class();
            if ($model->getFromDB($item->fields[$modelsfield])) {
                if ($model->fields['required_units'] > 1) {
                    $gs_item['height'] = $model->fields['required_units'];
                    $gs_item['y']      = $rack->fields['number_units'] + 1
                                    - $row['position']
                                    - $model->fields['required_units'];
                }

                if ($model->fields['is_half_rack'] == 1) {
                    $gs_item['half_rack'] = true;
                    $gs_item['width'] = 1;
                    $row['position'] .= "_" . $gs_item['x'];
                    if ($row['orientation'] == Rack::REAR) {
                        $gs_item['x'] = $row['hpos'] == 2 ? 0 : 1;
                    }
                }

                if (!empty($model->fields['picture_front'])) {
                    $gs_item['picture_f'] = Toolbox::getPictureUrl($model->fields['picture_front']);
                }
                if (!empty($model->fields['picture_rear'])) {
                    $gs_item['picture_r'] = Toolbox::getPictureUrl($model->fields['picture_rear']);
                }
            } else {
                $model = null;
            }

            if (isset($data[$row['orientation']][$position])) {
                $data[$row['orientation']][$row['position']] = [
                    'row'     => $row,
                    'item'    => $item,
                    'model'   => $model,
                    'gs_item' => $gs_item
                ];

               //add to other side if needed
                if (
                    $model == null
                    || $model->fields['depth'] >= 1
                ) {
                    $gs_item['rear'] = true;
                    $flip_orientation = (int) !((bool) $row['orientation']);
                    if ($gs_item['half_rack']) {
                        $gs_item['x'] = (int) !((bool) $gs_item['x']);
                       //$row['position'] = substr($row['position'], 0, -2)."_".$gs_item['x'];
                    }
                    $data[$flip_orientation][$row['position']] = [
                        'row'     => $row,
                        'item'    => $item,
                        'gs_item' => $gs_item
                    ];
                }
            } else {
                $outbound[] = [
                    'row'     => $row,
                    'item'    => $item,
                    'model'   => $model,
                    'gs_item' => $gs_item,
                ];
            }
        }

        if (count($outbound)) {
            echo "<table class='outbound'><thead><th>";
            echo __('Following elements are out of rack bounds');
            echo "</th></thead><tbody>";
            foreach ($outbound as $out) {
                echo "<tr><td>" . self::getCell($out, !$canedit) . "</td></tr>";
            }
            echo "</tbody></table>";
        }

        $nb_top_pdu = count(PDU_Rack::getForRackSide($rack, PDU_Rack::SIDE_TOP));
        $nb_bot_pdu = count(PDU_Rack::getForRackSide($rack, PDU_Rack::SIDE_BOTTOM));

        echo '
      <div class="racks_row">
         <span class="racks_view_controls">
            <span class="mini_toggle active"
                  id="toggle_images">' . __('images') . '</span>
            <span class="mini_toggle active"
                  id="toggle_text">' . __('texts') . '</span>
            <div class="clearfix"></div>
         </span>
         <div class="racks_col">
         <h2>' . __('Front') . '</h2>
         <div class="rack_side rack_front">';
       // append some spaces on top for having symetrical view between front and rear
        for ($i = 0; $i < $nb_top_pdu; $i++) {
            echo "<div class='virtual_pdu_space'></div>";
        }
        echo '<ul class="indexes"></ul>
            <div class="grid-stack grid-stack-2 grid-rack"
                 id="grid-front"
                 gs-column="2"
                 gs-max-row="' . ($rack->fields['number_units'] + 1) . '">';

        if ($link->canCreate()) {
            echo '<div class="racks_add"></div>';
        }

        foreach ($data[Rack::FRONT] as $current_item) {
            echo self::getCell($current_item, !$canedit);
        }
        echo '   <div class="grid-stack-item lock-bottom"
                    gs-no-resize="true" gs-no-move="true"
                    gs-h="1" gs-w="2" gs-x="0" gs-y="' . $rack->fields['number_units'] . '"></div>
            </div>
            <ul class="indexes"></ul>';
       // append some spaces on bottom for having symetrical view between front and rear
        for ($i = 0; $i < $nb_bot_pdu; $i++) {
            echo "<div class='virtual_pdu_space'></div>";
        }
        echo '</div>
         </div>
         <div class="racks_col">
            <h2>' . __('Rear') . '</h2>';
        echo '<div class="rack_side rack_rear">';
        PDU_Rack::showVizForRack($rack, PDU_Rack::SIDE_TOP);
        PDU_Rack::showVizForRack($rack, PDU_Rack::SIDE_LEFT);
        echo '<ul class="indexes"></ul>
            <div class="grid-stack grid-stack-2 grid-rack"
                 id="grid2-rear"
                 gs-column="2"
                 gs-max-row="' . ($rack->fields['number_units'] + 1) . '">';

        if ($link->canCreate()) {
            echo '<div class="racks_add"></div>';
        }

        foreach ($data[Rack::REAR] as $current_item) {
            echo self::getCell($current_item, !$canedit);
        }
        echo '   <div class="grid-stack-item lock-bottom"
                    gs-no-resize="true" gs-no-move="true"
                    gs-h="1" gs-w="2" gs-x="0" gs-y="' . $rack->fields['number_units'] . '">
               </div>
            </div>
            <ul class="indexes"></ul>';
        PDU_Rack::showVizForRack($rack, PDU_Rack::SIDE_RIGHT);
        PDU_Rack::showVizForRack($rack, PDU_Rack::SIDE_BOTTOM);
        echo '</div>';
        echo '
         </div>
         <div class="racks_col">';
        self::showStats($rack);
        PDU_Rack::showStatsForRack($rack);
        echo '</div>'; // .racks_col
        echo '</div>'; // .racks_row
        echo "<div id='grid-dialog'></div>";
        echo "</div>"; // #viewgraph

        $rack_add_tip = __s('Insert an item here');
        $ajax_url     = $CFG_GLPI['root_doc'] . "/ajax/rack.php";

        $js = <<<JAVASCRIPT
      // init variables to pass to js/rack.js
      var grid_link_url      = "{$link->getFormURL()}";
      var grid_item_ajax_url = "{$ajax_url}";
      var grid_rack_id       = $ID;
      var grid_rack_units    = {$rack->fields['number_units']};
      var grid_rack_add_tip  = "{$rack_add_tip}";

      $(function() {
         // initialize grid with function defined in js/rack.js
         initRack();
      });
JAVASCRIPT;
        echo Html::scriptBlock($js);
    }

    /**
     * Display a mini stats block (wiehgt, power, etc) for the current rack instance
     * @param  Rack   $rack the current rack instance
     * @return void
     */
    public static function showStats(Rack $rack)
    {
        global $DB;

        $items = $DB->request([
            'FROM'   => self::getTable(),
            'WHERE'  => [
                'racks_id' => $rack->getID()
            ]
        ]);

        $weight = 0;
        $power  = 0;
        $units  = [
            Rack::FRONT => array_fill(0, $rack->fields['number_units'], 0),
            Rack::REAR  => array_fill(0, $rack->fields['number_units'], 0),
        ];

        $rel = new self();
        foreach ($items as $row) {
            $rel->getFromDB($row['id']);

            $item = new $row['itemtype']();
            $item->getFromDB($row['items_id']);

            $model_class = $item->getType() . 'Model';
            $modelsfield = strtolower($item->getType()) . 'models_id';
            $model = new $model_class();

            if ($model->getFromDB($item->fields[$modelsfield])) {
                $required_units = $model->fields['required_units'];

                for ($i = 0; $i < $model->fields['required_units']; $i++) {
                    $units[$row['orientation']][$row['position'] + $i] = 1;
                    if ($model->fields['depth'] == 1) {
                        $other_side = (int) !(bool) $row['orientation'];
                        $units[$other_side][$row['position'] + $i] = 1;
                    }
                }

                if (array_key_exists('power_consumption', $model->fields)) { // PDU does not consume energy
                    $power += $model->fields['power_consumption'];
                }

                $weight += $model->fields['weight'];
            } else {
                $units[Rack::FRONT][$row['position']] = 1;
                $units[Rack::REAR][$row['position']]  = 1;
            }
        }

        $nb_units = max(
            array_sum($units[Rack::FRONT]),
            array_sum($units[Rack::REAR])
        );

        $space_prct  = round(100 * $nb_units / max($rack->fields['number_units'], 1));
        $weight_prct = round(100 * $weight / max($rack->fields['max_weight'], 1));
        $power_prct  = round(100 * $power / max($rack->fields['max_power'], 1));

        echo "<div id='rack_stats' class='rack_side_block'>";

        echo "<h2>" . __("Rack stats") . "</h2>";

        echo "<div class='rack_side_block_content'>";
        echo "<h3>" . __("Space") . "</h3>";
        Html::progressBar('rack_space', [
            'create' => true,
            'percent' => $space_prct,
            'message' => $space_prct . "%",
        ]);

        echo "<h3>" . __("Weight") . "</h3>";
        Html::progressBar('rack_weight', [
            'create' => true,
            'percent' => $weight_prct,
            'message' => $weight . " / " . $rack->fields['max_weight']
        ]);

        echo "<h3>" . __("Power") . "</h3>";
        Html::progressBar('rack_power', [
            'create' => true,
            'percent' => $power_prct,
            'message' => $power . " / " . $rack->fields['max_power']
        ]);
        echo "</div>";
        echo "</div>";
    }

    public function showForm($ID, array $options = [])
    {
        global $DB, $CFG_GLPI;

        $colspan = 4;

        echo "<div class='center'>";

        $this->initForm($ID, $options);
        $this->showFormHeader();

        $rack = new Rack();
        $rack->getFromDB($this->fields['racks_id']);

        $rand = mt_rand();

        echo "<tr class='tab_bg_1'>";
        echo "<td><label for='dropdown_itemtype$rand'>" . __('Item type') . "</label></td>";
        echo "<td>";

        if (isset($options['_onlypdu']) && $options['_onlypdu']) {
            $this->fields['itemtype'] = 'PDU';
            echo Html::hidden(
                'itemtype',
                [
                    'id'    => "itemtype_$rand",
                    'value' => 'PDU'
                ]
            );
            echo PDU::getTypeName(1);
        } else {
            $types = array_combine($CFG_GLPI['rackable_types'], $CFG_GLPI['rackable_types']);
            foreach ($types as $type => &$text) {
                $text = $type::getTypeName(1);
            }
            Dropdown::showFromArray(
                'itemtype',
                $types,
                [
                    'display_emptychoice'   => true,
                    'value'                 => $this->fields["itemtype"],
                    'rand'                  => $rand
                ]
            );
        }

       //get all used items
        $used = $used_reserved = [];
        $iterator = $DB->request([
            'FROM' => $this->getTable()
        ]);
        foreach ($iterator as $row) {
            $used[$row['itemtype']][] = $row['items_id'];
        }
       // find used pdu (not racked)
        foreach (PDU_Rack::getUsed() as $used_pdu) {
            $used['PDU'][] = $used_pdu['pdus_id'];
        }
       // get all reserved items
        $iterator = $DB->request([
            'FROM'  => $this->getTable(),
            'WHERE' => [
                'is_reserved' => true
            ]
        ]);
        foreach ($iterator as $row) {
            $used_reserved[$row['itemtype']][] = $row['items_id'];
        }

       //items part of an enclosure should not be listed
        $iterator = $DB->request([
            'FROM'   => Item_Enclosure::getTable()
        ]);
        foreach ($iterator as $row) {
            $used[$row['itemtype']][] = $row['items_id'];
        }
        echo Html::hidden(
            'used',
            [
                'id'    => "used_$rand",
                'value' => json_encode($used)
            ]
        );

       //TODO: update possible positions according to selected item number of units
       //TODO: update positions on rack selection
       //TODO: update hpos from item model info is_half_rack
       //TODO: update orientation according to item model depth

        echo "</td>";
        echo "<td><label for='dropdown_items_id$rand'>" . _n('Item', 'Items', 1) . "</label></td>";
        echo "<td id='items_id'>";
        if (isset($this->fields['itemtype']) && !empty($this->fields['itemtype'])) {
            $itemtype = $this->fields['itemtype'];
            $itemtype = new $itemtype();
            $itemtype::dropdown([
                'name'   => "items_id",
                'value'  => $this->fields['items_id'],
                'rand'   => $rand
            ]);
        } else {
            Dropdown::showFromArray(
                'items_id',
                [],
                [
                    'display_emptychoice'   => true,
                    'rand'                  => $rand
                ]
            );
        }

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

        echo "<tr class='tab_bg_1'>";
        echo "<td><label for='dropdown_racks_id$rand'>" . Rack::getTypeName(1) . "</label></td>";
        echo "<td>";
        Rack::dropdown(['value' => $this->fields["racks_id"], 'rand' => $rand]);
        echo "</td>";
        echo "<td><label for='dropdown_position$rand'>" . __('Position') . "</label></td>";
        echo "<td >";
        Dropdown::showNumber(
            'position',
            [
                'value'  => $this->fields["position"],
                'min'    => 1,
                'max'    => $rack->fields['number_units'],
                'step'   => 1,
                'used'   => $rack->getFilled($this->fields['itemtype'], $this->fields['items_id']),
                'rand'   => $rand
            ]
        );
        echo "</td>";
        echo "</tr>";

        echo "<tr class='tab_bg_1'>";
        echo "<td><label for='dropdown_orientation$rand'>" . __('Orientation (front rack point of view)') . "</label></td>";
        echo "<td >";
        Dropdown::showFromArray(
            'orientation',
            [
                Rack::FRONT => __('Front'),
                Rack::REAR  => __('Rear')
            ],
            [
                'value' => $this->fields["orientation"],
                'rand' => $rand
            ]
        );
        echo "</td>";
        echo "<td><label for='bgcolor$rand'>" . __('Background color') . "</label></td>";
        echo "<td>";
        Html::showColorField(
            'bgcolor',
            [
                'value'  => $this->fields['bgcolor'],
                'rand'   => $rand
            ]
        );
        echo "</td>";
        echo "</tr>";

        echo "<tr class='tab_bg_1'>";
        echo "<td><label for='dropdown_hpos$rand'>" . __('Horizontal position (from rack point of view)') . "</label></td>";
        echo "<td>";
        Dropdown::showFromArray(
            'hpos',
            [
                Rack::POS_NONE    => __('None'),
                Rack::POS_LEFT    => __('Left'),
                Rack::POS_RIGHT   => __('Right')
            ],
            [
                'value'  => $this->fields['hpos'],
                'rand'   => $rand
            ]
        );
        echo "</td>";
        echo "<td><label for='dropdown_is_reserved$rand'>" . __('Reserved position?') . "</label></td>";
        echo "<td>";

        echo Html::scriptBlock("
         var toggleUsed = function(reserved) {
            if (reserved == 1) {
               $('#used_$rand').val('" . json_encode($used_reserved) . "');
            } else {
               $('#used_$rand').val('" . json_encode($used) . "');
            }
            // force change of itemtype dropdown to have a correct (with empty/filled used input)
            // filtered items list
            $('#dropdown_itemtype$rand').trigger('change');
         }
      ");
        Dropdown::showYesNo(
            'is_reserved',
            $this->fields['is_reserved'],
            -1,
            [
                'rand'      => $rand,
                'on_change' => 'toggleUsed(this.value)'
            ]
        );

        $entities = $rack->fields['entities_id'];
        if ($rack->fields['is_recursive']) {
            $entities = getSonsOf('glpi_entities', $entities);
        }

        Ajax::updateItemOnSelectEvent(
            ["dropdown_itemtype$rand", "dropdown_is_reserved$rand", "used_$rand"],
            "items_id",
            $CFG_GLPI["root_doc"] . "/ajax/dropdownAllItems.php",
            [
                'idtable'         => '__VALUE0__',
                'name'            => 'items_id',
                'value'           => $this->fields['items_id'],
                'rand'            => $rand,
                'is_reserved'     => '__VALUE1__',
                'used'            => '__VALUE2__',
                'entity_restrict' => $entities,
            ]
        );
        echo "</td>";
        echo "</tr>";

        $this->showFormButtons($options);

        return true;
    }

    public function post_getEmpty()
    {
        $this->fields['bgcolor'] = '#69CEBA';
    }

    /**
     * Get cell content
     *
     * @param mixed $cell Rack cell (array or false)
     *
     * @return string
     */
    private static function getCell($cell, $readonly = false)
    {
        if ($cell) {
            $item        = $cell['item'];
            $gs_item     = $cell['gs_item'];
            $name        = $gs_item['name'];
            $typename    = is_object($item)
                         ? $item->getTypeName()
                         : "";
            $serial      = is_object($item)
                         ? $item->fields['serial']
                         : "";
            $otherserial = is_object($item)
                         ? $item->fields['otherserial']
                         : "";
            $model       = is_object($item)
                        && isset($cell['model'])
                        && is_object($cell['model'])
                        && isset($cell['model']->fields['name'])
                         ? $cell['model']->fields['name']
                         : '';
            $rear        = $gs_item['rear'];
            $back_class  = $rear
                         ? "item_rear"
                         : "item_front";
            $half_class  = $gs_item['half_rack']
                         ? "half_rack"
                         : "";
            $reserved    = $gs_item['reserved'];
            $reserved_cl = $reserved
                         ? "reserved"
                         : "";
            $icon        = $reserved
                         ? self::getItemIcon("Reserved")
                         : self::getItemIcon(get_class($item));
            $bg_color    = $gs_item['bgcolor'];
            if ($item->maybeDeleted() && $item->isDeleted()) {
                $bg_color = '#ff0000'; //red for deleted items
            }
            $fg_color    = !empty($bg_color)
                         ? Html::getInvertedColor($gs_item['bgcolor'])
                         : "";
            $fg_color_s  = "color: $fg_color;";
            $img_class   = "";
            $img_s       = "";
            if ($gs_item['picture_f'] && !$rear && !$reserved) {
                $img_s = "background: $bg_color url(\"" . $gs_item['picture_f'] . "\")  no-repeat top left/100% 100%;";
                $img_class = 'with_picture';
            }
            if ($gs_item['picture_r'] && $rear && !$reserved) {
                $img_s = "background: $bg_color url(\"" . $gs_item['picture_r'] . "\")  no-repeat top left/100% 100%;";
                $img_class = 'with_picture';
            }

            $tip = "<span class='tipcontent'>";
            $tip .= "<span>
                  <label>" .
                  ($rear
                     ? __("asset rear side")
                     : __("asset front side")) . "
                  </label>
               </span>";
            if (!empty($typename)) {
                 $tip .= "<span>
                     <label>" . _n('Type', 'Types', 1) . ":</label>
                     $typename
                  </span>";
            }
            if (!empty($name)) {
                $tip .= "<span>
                     <label>" . __('name') . ":</label>
                     $name
                  </span>";
            }
            if (!empty($serial)) {
                $tip .= "<span>
                     <label>" . __('serial') . ":</label>
                     $serial
                  </span>";
            }
            if (!empty($otherserial)) {
                $tip .= "<span>
                     <label>" . __('Inventory number') . ":</label>
                     $otherserial
                  </span>";
            }
            if (!empty($model)) {
                $tip .= "<span>
                     <label>" . __('model') . ":</label>
                     $model
                  </span>";
            }

            $tip .= "</span>";

            $readonly_attr = $readonly ? 'gs-no-move="true"' : '';
            return "
         <div class='grid-stack-item pdu-grid {$back_class} {$half_class} {$reserved_cl} {$img_class}'
               gs-w='{$gs_item['width']}' gs-h='{$gs_item['height']}'
               gs-x='{$gs_item['x']}'     gs-y='{$gs_item['y']}'
               gs-id='{$gs_item['id']}'   gs-locked='true' {$readonly_attr}
               style='background-color: $bg_color; color: $fg_color;'>
            <div class='grid-stack-item-content' style='$fg_color_s $img_s'>
               $icon" .
               (!empty($gs_item['url'])
                  ? "<a href='{$gs_item['url']}' class='itemrack_name' style='$fg_color_s'>{$gs_item['name']}</a>"
                  : "<span class='itemrack_name'>" . $gs_item['name'] . "</span>") . "
               <a href='{$gs_item['rel_url']}'>
                  <i class='fa fa-pencil-alt rel-link'
                     style='$fg_color_s'
                     title='" . __("Edit rack relation") . "'></i>
               </a>
               $tip
            </div>
         </div>";
        }

        return false;
    }


    /**
     * Return an i html tag with a dedicated icon for the itemtype
     * @param  string $itemtype  A rackable itemtype
     * @return string           The i html tag
     */
    private static function getItemIcon($itemtype = "")
    {
        $icon = "";
        switch ($itemtype) {
            case "Computer":
                $icon = "ti ti-server";
                break;
            case "Reserved":
                $icon = "ti ti-lock";
                break;

            default:
                $icon = $itemtype::getIcon();
                break;
        }

        if (!empty($icon)) {
            $icon = "<i class='item_rack_icon $icon'></i>";
        }

        return $icon;
    }

    public function prepareInputForAdd($input)
    {
        return $this->prepareInput($input);
    }

    public function prepareInputForUpdate($input)
    {
        return $this->prepareInput($input);
    }

    /**
     * Prepares input (for update and add)
     *
     * @param array $input Input data
     *
     * @return array
     */
    private function prepareInput($input)
    {
        $error_detected = [];

        $itemtype    = !$this->isNewItem() ? $this->fields['itemtype'] : null;
        $items_id    = !$this->isNewItem() ? $this->fields['items_id'] : null;
        $racks_id    = !$this->isNewItem() ? $this->fields['racks_id'] : null;
        $position    = !$this->isNewItem() ? $this->fields['position'] : null;
        $hpos        = !$this->isNewItem() ? $this->fields['hpos'] : null;
        $orientation = !$this->isNewItem() ? $this->fields['orientation'] : null;

       //check for requirements
        if (
            ($this->isNewItem() && (!isset($input['itemtype']) || empty($input['itemtype'])))
            || (isset($input['itemtype']) && empty($input['itemtype']))
        ) {
            $error_detected[] = __('An item type is required');
        }
        if (
            ($this->isNewItem() && (!isset($input['items_id']) || empty($input['items_id'])))
            || (isset($input['items_id']) && empty($input['items_id']))
        ) {
            $error_detected[] = __('An item is required');
        }
        if (
            ($this->isNewItem() && (!isset($input['racks_id']) || empty($input['racks_id'])))
            || (isset($input['racks_id']) && empty($input['racks_id']))
        ) {
            $error_detected[] = __('A rack is required');
        }
        if (
            ($this->isNewItem() && (!isset($input['position']) || empty($input['position'])))
            || (isset($input['position']) && empty($input['position']))
        ) {
            $error_detected[] = __('A position is required');
        }

        if (isset($input['itemtype'])) {
            $itemtype = $input['itemtype'];
        }
        if (isset($input['items_id'])) {
            $items_id = $input['items_id'];
        }
        if (isset($input['racks_id'])) {
            $racks_id = $input['racks_id'];
        }
        if (isset($input['position'])) {
            $position = $input['position'];
        }
        if (isset($input['hpos'])) {
            $hpos = $input['hpos'];
        }
        if (isset($input['orientation'])) {
            $orientation = $input['orientation'];
        }

        if (!count($error_detected)) {
           //check if required U are available at position
            $rack = new Rack();
            $rack->getFromDB($racks_id);

            if ($this->isNewItem()) {
                $filled = $rack->getFilled();
            } else {
               // If object is existing, exclude current state from used positions
                $filled = $rack->getFilled($this->fields['itemtype'], $this->fields['items_id']);
            }

            $item = new $itemtype();
            $item->getFromDB($items_id);
            $model_class = $item->getType() . 'Model';
            $modelsfield = strtolower($item->getType()) . 'models_id';
            $model = new $model_class();

            $required_units = 1;
            $width          = 1;
            $depth          = 1;
            if ($model->getFromDB($item->fields[$modelsfield])) {
                if ($model->fields['required_units'] > 1) {
                    $required_units = $model->fields['required_units'];
                }
                if ($model->fields['is_half_rack'] == 1) {
                    if ($this->isNewItem() && !isset($input['hpos']) || $input['hpos'] == 0) {
                        $error_detected[] = __('You must define an horizontal position for this item');
                    }
                    $width = 0.5;
                }
                if ($model->fields['depth'] != 1) {
                    if ($this->isNewItem() && !isset($input['orientation'])) {
                        $error_detected[] = __('You must define an orientation for this item');
                    }
                    $depth = $model->fields['depth'];
                }
            }

            if (
                $position > $rack->fields['number_units'] ||
                $position + $required_units  > $rack->fields['number_units'] + 1
            ) {
                $error_detected[] = __('Item is out of rack bounds');
            } else if (!count($error_detected)) {
                $i = 0;
                while ($i < $required_units) {
                    $current_position = $position + $i;
                    if (isset($filled[$current_position])) {
                        $content_filled = $filled[$current_position];

                        if ($hpos == Rack::POS_NONE || $hpos == Rack::POS_LEFT) {
                            $d = 0;
                            while ($d / 4 < $depth) {
                                $pos = ($orientation == Rack::REAR) ? 3 - $d : $d;
                                $val = 1;
                                if (isset($content_filled[Rack::POS_LEFT][$pos]) && $content_filled[Rack::POS_LEFT][$pos] != 0) {
                                    $error_detected[] = __('Not enough space available to place item');
                                    break 2;
                                }
                                ++$d;
                            }
                        }

                        if ($hpos == Rack::POS_NONE || $hpos == Rack::POS_RIGHT) {
                            $d = 0;
                            while ($d / 4 < $depth) {
                                $pos = ($orientation == Rack::REAR) ? 3 - $d : $d;
                                $val = 1;
                                if (isset($content_filled[Rack::POS_RIGHT][$pos]) && $content_filled[Rack::POS_RIGHT][$pos] != 0) {
                                    $error_detected[] = __('Not enough space available to place item');
                                    break 2;
                                }
                                ++$d;
                            }
                        }
                    }
                    ++$i;
                }
            }
        }

        if (count($error_detected)) {
            foreach ($error_detected as $error) {
                Session::addMessageAfterRedirect(
                    $error,
                    true,
                    ERROR
                );
            }
            return false;
        }

        return $input;
    }

    protected function computeFriendlyName()
    {
        $rack = new Rack();
        $rack->getFromDB($this->fields['racks_id']);
        $name = sprintf(
            __('Item for rack "%1$s"'),
            $rack->getName()
        );

        return $name;
    }


    public static function getIcon()
    {
        return Rack::getIcon();
    }
}
