<?php
namespace idoit\Module\Report\SqlQuery\Structure;

use idoit\Module\Report\SqlQuery\Condition\Comparison\InCondition;
use idoit\Module\Report\SqlQuery\Condition\Comparison\LikeCondition;
use idoit\Module\Report\SqlQuery\Condition\Comparison\NotInCondition;
use idoit\Module\Report\SqlQuery\Condition\Comparison\NotNullCondition;
use idoit\Module\Report\SqlQuery\Condition\Comparison\NullCondition;
use idoit\Module\Report\SqlQuery\Condition\Filter\AbstractFilterProcessorValue;
use idoit\Module\Report\SqlQuery\Condition\Filter\Filter;
use idoit\Module\Report\SqlQuery\Condition\Filter\FilterProcessorConditionEmptyValue;
use isys_cmdb_dao;

/**
 * Container Class for a report query
 *
 * Class ReportQuery
 */
class ReportQuery
{
    /**
     * @var isys_cmdb_dao
     */
    private $dao;

    /**
     * @var string
     */
    private $query = '';

    /**
     * @var array
     */
    private $rootProperties = [];

    /**
     * @var array
     */
    private $subProperties = [];

    /**
     * @var array
     */
    private $conditions = [];

    /**
     * @var array
     */
    private $selections = [];

    /**
     * @var array
     */
    private $joins = [];

    /**
     * @var string
     */
    private $sorting = '';

    /**
     * @var int
     */
    private $limit;

    /**
     * @var Filter[]
     */
    private $dynamicFilter = [];

    /**
     * @return int
     */
    public function getLimit()
    {
        return $this->limit;
    }

    /**
     * @param int $limit
     *
     * @return ReportQuery
     */
    public function setLimit($limit)
    {
        $this->limit = $limit;

        return $this;
    }

    /**
     * @return string
     */
    public function getQuery()
    {
        return $this->query;
    }

    /**
     * @param string $query
     */
    public function setQuery($query)
    {
        $this->query = $query;
        return $this;
    }

    /**
     * @return array
     */
    public function getRootProperties()
    {
        return $this->rootProperties;
    }

    /**
     * @param array $rootProperties
     *
     * @return ReportQuery
     */
    public function setRootProperties($rootProperties)
    {
        $this->rootProperties = $rootProperties;

        return $this;
    }

    /**
     * @return array
     */
    public function getSubProperties()
    {
        return $this->subProperties;
    }

    /**
     * @param array $subProperties
     *
     * @return ReportQuery
     */
    public function setSubProperties($subProperties)
    {
        $this->subProperties = $subProperties;

        return $this;
    }

    /**
     * @return array
     */
    public function getConditions()
    {
        return $this->conditions;
    }

    /**
     * @param array $conditions
     *
     * @return ReportQuery
     */
    public function setConditions($conditions)
    {
        $this->conditions = $conditions;

        return $this;
    }

    /**
     * @param string $condition
     *
     * @return ReportQuery
     */
    public function addCondition($condition)
    {
        $this->conditions[] = $condition;

        return $this;
    }

    /**
     * @return array
     */
    public function getSelections()
    {
        return $this->selections;
    }

    /**
     * @param array $selections
     *
     * @return ReportQuery
     */
    public function setSelections($selections)
    {
        $this->selections = $selections;

        return $this;
    }

    /**
     * @param string $selection
     *
     * @return ReportQuery
     */
    public function addSelection($selection)
    {
        $this->selections[] = $selection;

        return $this;
    }

    /**
     * @return array
     */
    public function getJoins()
    {
        return $this->joins;
    }

    /**
     * @param array $joins
     *
     * @return ReportQuery
     */
    public function setJoins($joins)
    {
        $this->joins = $joins;

        return $this;
    }

    /**
     * @param string $alias
     * @param string $join
     *
     * @return ReportQuery
     */
    public function addJoin($alias, $join)
    {
        $this->joins[$alias] = $join;

        return $this;
    }

    /**
     * @return string
     */
    public function getSorting()
    {
        return $this->sorting;
    }

    /**
     * @param string $sorting
     *
     * @return ReportQuery
     */
    public function setSorting($sorting)
    {
        $this->sorting = $sorting;

        return $this;
    }

    /**
     * @return array
     */
    public function getDynamicFilter()
    {
        return $this->dynamicFilter;
    }

    /**
     * @param array $dynamicFilter
     *
     * @return ReportQuery
     */
    public function setDynamicFilter($dynamicFilter)
    {
        $this->dynamicFilter = $dynamicFilter;

        return $this;
    }

    /**
     * @param Filter $conditionFilter
     *
     * @return ReportQuery
     */
    public function addDynamicFilter(Filter $conditionFilter)
    {
        $this->dynamicFilter[] = $conditionFilter;
        return $this;
    }

    /**
     * @return isys_cmdb_dao
     */
    public function getDao()
    {
        return $this->dao;
    }

    /**
     * @param isys_cmdb_dao $dao
     *
     * @return ReportQuery
     */
    public function setDao($dao)
    {
        $this->dao = $dao;

        return $this;
    }

    /**
     * @param isys_cmdb_dao $dao
     * @param array         $rootProperties
     * @param array         $subProperties
     * @param array         $selections
     * @param array         $joins
     * @param array         $conditions
     * @param string        $sorting
     * @param null          $limit
     *
     * @return ReportQuery
     */
    public static function factory(isys_cmdb_dao $dao, array $rootProperties = [], array $subProperties = [], array $selections = [], array $joins = [], array $conditions = [], $sorting = '', $limit = null)
    {
        return (new self)
            ->setDao($dao)
            ->setRootProperties($rootProperties)
            ->setSubProperties($subProperties)
            ->setSelections($selections)
            ->setJoins($joins)
            ->setConditions($conditions)
            ->setSorting($sorting)
            ->setLimit($limit);
    }

    /**
     * Build the query
     *
     * @return ReportQuery
     */
    public function buildQuery()
    {
        $query = "SELECT \n" . implode(", \n", $this->selections) . " \n\n" . " FROM isys_obj AS obj_main \n" . implode(" \n", $this->joins) . " \n\n" . "WHERE TRUE \n"
            . rtrim(implode(" \n", $this->conditions), 'AND OR') . "\n";

        $query .= $this->sorting;

        if ($this->limit > 0) {
            $query .= ' LIMIT 0, ' . $this->limit;
        }

        $this->setQuery($query);
        return $this;
    }

    /**
     * @return void
     * @throws \isys_exception_database
     */
    public function processDynamicFilters()
    {
        // Process Query and iterate through each entry
        if (!empty($this->dynamicFilter)) {
            $dynamicFilters = array_reverse($this->dynamicFilter);
            $sorting = $this->getSorting();

            $orderyByArr = [];
            foreach ($dynamicFilters as $filter) {
                $orderyByArr[] = $filter->getField();
            }

            $this->setSorting(' ORDER BY ' . implode(', ', $orderyByArr) . ' DESC ');
            if (empty($this->query)) {
                $this->buildQuery();
            }

            $result = $this->dao->retrieve($this->query);

            $this->setSorting($sorting)->buildQuery();

            $startTime = microtime(true);

            while ($row = $result->get_row()) {
                foreach ($dynamicFilters as $filter) {
                    $value = $row[$filter->getKey()];

                    if (empty($value) || $filter->getProcessor()->processedIdExists($value)) {
                        continue;
                    }

                    $filter->getProcessor()
                        ->setId($value);

                    $filter->process();

                    $filter->getProcessor()->addProcessedId($value);
                }
            }

            foreach ($dynamicFilters as $filter) {
                $this->query = str_replace('TRUE/*' . $filter->getKey() . '*/', $this->buildDynamicCondition($filter), $this->query);
            }
        }
    }

    /**
     * @param Filter $filter
     *
     * @return string
     */
    private function buildDynamicCondition(Filter $filter)
    {
        $processedValueIdsPositive = $filter->getProcessor()->getProcessedValueIdsPositive();
        $processedValueIdsNegative = $filter->getProcessor()->getProcessedValueIdsNegative();
        $additionalCondtionOperator = '';
        $additionalCondition = '';

        if ($filter->getCondition() instanceof LikeCondition) {
            if (!empty($processedValueIdsPositive)) {
                $conditions[] = (new InCondition())
                    ->setConditionValue(implode(',', $processedValueIdsPositive))
                    ->setConditionField($filter->getCondition()->getConditionField())
                    ->format();
            } elseif (!empty($processedValueIdsNegative)) {
                $conditions[] = (new NotInCondition())
                    ->setConditionValue(implode(',', $processedValueIdsNegative))
                    ->setConditionField($filter->getCondition()->getConditionField())
                    ->format();
            }

            if ($filter->getProcessor()->getProcessorConditionValue() instanceof FilterProcessorConditionEmptyValue) {
                $additionalCondition = (new NullCondition())
                    ->setConditionField($filter->getCondition()->getConditionField())
                    ->format();
                $additionalCondtionOperator = ' OR ';
            }
        } else {
            if (!empty($processedValueIdsPositive)) {
                $conditions[] = (new NotInCondition())
                    ->setConditionValue(implode(',', $processedValueIdsPositive))
                    ->setConditionField($filter->getCondition()->getConditionField())
                    ->format();
            } elseif (!empty($processedValueIdsNegative)) {
                $conditions[] = (new InCondition())
                    ->setConditionValue(implode(',', $processedValueIdsNegative))
                    ->setConditionField($filter->getCondition()->getConditionField())
                    ->format();
            }

            if ($filter->getProcessor()->getProcessorConditionValue() instanceof FilterProcessorConditionEmptyValue) {
                $additionalCondition = (new NotNullCondition())
                    ->setConditionField($filter->getCondition()->getConditionField())
                    ->format();
                $additionalCondtionOperator = ' AND ';
            }
        }

        if (!empty($conditions)) {
            $condition = ' (' . implode(' AND ', $conditions) . ') ' . $additionalCondtionOperator . ' ' . $additionalCondition;
        } elseif ($additionalCondition !== '') {
            $condition = $additionalCondition;
        } else {
            $condition = 'FALSE';
        }

        return ' (' . $condition . ') ';
    }

    /**
     * @return string
     */
    public function __toString()
    {
        if (empty($this->query)) {
            $this->buildQuery();
        }
        return $this->query;
    }
}
