<?php

namespace InfluxDB\Query;

use InfluxDB\Database;
use InfluxDB\ResultSet;

/**
 * Class Builder
 *
 * Abstraction class for getting time series out of InfluxDB
 *
 * Sample usage:
 *
 * $series = new QueryBuilder($db);
 * $series->percentile(95)->setTimeRange($timeFrom, $timeTo)->getResult();
 *
 * $series->select('*')->from('*')->getResult();
 *
 * @todo add inner join
 * @todo add merge
 *
 * @package InfluxDB\Query
 * @author Stephen "TheCodeAssassin" Hoogendijk <s.hoogendijk@tech.leaseweb.com>
 */
class Builder
{
    /**
     * @var Database
     */
    protected $db;

    /**
     * @var string
     */
    protected $selection = '*';

    /**
     * @var string[]
     */
    protected $where = array();

    /**
     * @var string
     */
    protected $startTime;

    /**
     * @var string
     */
    protected $endTime;

    /**
     * @var string
     */
    protected $retentionPolicy;

    /**
     * @var string
     */
    protected $metric;

    /**
     * @var string
     */
    protected $limitClause = '';

    /**
     * @var string
     */
    protected $offsetClause = '';

    /**
     * @var array
     */
    protected $groupBy;

    /**
     * @var array
     */
    protected $orderBy;

    /**
     * @param Database $db
     */
    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    /**
     * @param  string $metric The metric to select (required)
     * @return $this
     */
    public function from($metric)
    {
        $this->metric = $metric;

        return $this;
    }

    /**
     * Custom select method
     *
     * example:
     *
     * $series->select('sum(value)',
     *
     * @param  string $customSelect
     * @return $this
     */
    public function select($customSelect)
    {
        $this->selection = $customSelect;

        return $this;
    }

    /**
     * @param array $conditions
     *
     * Example: array('time > now()', 'time < now() -1d');
     *
     * @return $this
     */
    public function where(array $conditions)
    {
        foreach ($conditions as $condition) {
            $this->where[] = $condition;
        }

        return $this;
    }

    /**
     * @param  string $field
     * @return $this
     */
    public function count($field = 'type')
    {
        $this->selection = sprintf('count(%s)', $field);

        return $this;
    }

    /**
     * @param  string $field
     * @return $this
     */
    public function median($field = 'type')
    {
        $this->selection = sprintf('median(%s)', $field);

        return $this;
    }

    /**
     * @param  string $field
     * @return $this
     */
    public function mean($field = 'type')
    {
        $this->selection = sprintf('mean(%s)', $field);

        return $this;
    }

    /**
     * @param  string $field
     * @return $this
     */
    public function sum($field = 'type')
    {
        $this->selection = sprintf('sum(%s)', $field);

        return $this;
    }

    /**
     * @param  string $field
     * @return $this
     */
    public function first($field = 'type')
    {
        $this->selection = sprintf('first(%s)', $field);

        return $this;
    }

    /**
     * @param  string $field
     * @return $this
     */
    public function last($field = 'type')
    {
        $this->selection = sprintf('last(%s)', $field);

        return $this;
    }

    public function groupBy($field = 'type') {
        $this->groupBy[] = $field;

        return $this;
    }

    public function orderBy($field = 'type', $order = 'ASC') {
        $this->orderBy[] = "$field $order";

        return $this;
    }

    /**
     * Set's the time range to select data from
     *
     * @param  int $from
     * @param  int $to
     * @return $this
     */
    public function setTimeRange($from, $to)
    {
        $fromDate = date('Y-m-d H:i:s', (int) $from);
        $toDate = date('Y-m-d H:i:s', (int) $to);

        $this->where(array("time > '$fromDate'", "time < '$toDate'"));

        return $this;
    }

    /**
     * @param int $percentile Percentage to select (for example 95 for 95th percentile billing)
     *
     * @return $this
     */
    public function percentile($percentile = 95)
    {
        $this->selection = sprintf('percentile(value, %d)', (int) $percentile);

        return $this;
    }

    /**
     * Limit the ResultSet to n records
     *
     * @param int $count
     *
     * @return $this
     */
    public function limit($count)
    {
        $this->limitClause = sprintf(' LIMIT %s', (int) $count);

        return $this;
    }

    /**
     * Offset the ResultSet to n records
     *
     * @param int $count
     *
     * @return $this
     */
    public function offset($count)
    {
        $this->offsetClause = sprintf(' OFFSET %s', (int) $count);

        return $this;
    }

    /**
     * Add retention policy to query
     *
     * @param string $rp
     *
     * @return $this
     */
    public function retentionPolicy($rp)
    {
        $this->retentionPolicy =  $rp;

        return $this;
    }

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

    /**
     * Gets the result from the database (builds the query)
     *
     * @return ResultSet
     * @throws \Exception
     */
    public function getResultSet()
    {
        return  $this->db->query($this->parseQuery());
    }

    /**
     * @return string
     */
    protected function parseQuery()
    {
        $rp = '';

        if (is_string($this->retentionPolicy) && !empty($this->retentionPolicy)) {
            $rp = sprintf('"%s".', $this->retentionPolicy);
        }

        $query = sprintf('SELECT %s FROM %s"%s"', $this->selection, $rp, $this->metric);

        if (! $this->metric) {
            throw new \InvalidArgumentException('No metric provided to from()');
        }

        for ($i = 0, $iMax = count($this->where); $i < $iMax; $i++) {
            $selection = 'WHERE';

            if ($i > 0) {
                $selection = 'AND';
            }

            $clause = $this->where[$i];
            $query .= ' ' . $selection . ' ' . $clause;

        }

        if (!empty($this->groupBy)) {
            $query .= ' GROUP BY ' . implode(',', $this->groupBy);
        }

        if (!empty($this->orderBy)) {
            $query .= ' ORDER BY ' . implode(',', $this->orderBy);
        }

        if ($this->limitClause) {
            $query .= $this->limitClause;
        }

        if ($this->offsetClause) {
            $query .= $this->offsetClause;
        }

        return $query;
    }
}
