<?php
namespace App\Service;

use Stringy\Stringy;

class Factory
{
  protected $class;
  protected $is_aggregator;
  public function __construct($class)
  {
    $this->class = $class;
    $this->is_aggregator = true;
    if (is_subclass_of($class, 'Model')) {
      $this->is_aggregator = false;
    }
  }
  public function new()
  {
    $class = $this->class;
    if ($this->is_aggregator) {
      return new $class();
    }
    return model($class)->create();
  }
  public function create($data)
  {
    $class = $this->class;
    if ($this->is_aggregator) {
      $obj = new $class();
      $obj->create($data);
      return $obj;
    }
    return model($class)->create($data);
  }
  /**
   * [column => value, column => [value, operator]]
   * @var array
   */
  protected $conditions;
  public function where(array $data)
  {
    if ($this->conditions == null) {
      $this->conditions = $data;
      return $this;
    }
    $this->conditions = array_merge($this->conditions, $data);
    return $this;
  }
  /**
   * [column, column => order]
   * @var array
   */
  protected $order;
  public function order(array $data)
  {
    if ($this->order == null) {
      $this->order = $data;
      return $this;
    }
    $this->order = array_merge($this->order, $data);
    return $this;
  }
  protected $limit;
  public function limit(array $data)
  {
    if (!isset($data['limit'])) {
      $data['limit'] = $data[0];
    }
    if (!isset($data['offset'])) {
      $data['offset'] = 0;
      if (isset($data[1])) {
        $data['offset'] = $data[1];
      }
    }
    $this->limit = (object) ['limit' => $data['limit'], 'offset' => $data['offset']];
    return $this;
  }
  protected $many;
  public function find($many = false)
  {
    $this->many = $many;
    if ($this->is_aggregator) {
      return $this->findAggregator();
    }
    return $this->findModel();
  }

  protected function findModel()
  {
    $objs = model($this->class);
    $objs = $this->parseLimit($this->parseOrder($this->parseConditions($objs)));

    if ($this->many) {
      return $objs->findMany();
    }
    return $objs->findOne();
  }
  protected function parseConditions($orm)
  {
    if ($this->conditions == null) {
      return $orm;
    }
    foreach ($this->conditions as $column => $value) {
      if (is_array($value)) {
        list($value, $op) = $value;
        switch ($op) {
          case '>':
          case 'gt':
            $orm = $orm->whereGt($column, $value);
          break;
        }
      } else {
        $orm = $orm->where($column, $value);
      }
    }
    return $orm;
  }
  protected function parseOrder($orm)
  {
    if ($this->order == null) {
      return $orm;
    }
    foreach ($this->order as $column => $order) {
      if (is_numeric($column)) {
        $column = $order;
        $order = 'asc';
      }
      switch (strtolower($order)) {
        case 'asc':
        default:
          $orm = $orm->orderByAsc($column);
          break;
        case 'desc':
          $orm = $orm->orderByDesc($column);
          break;
        case 'expr':
          $orm = $orm->orderByExpr($column);
          break;
      }
    }
    return $orm;
  }
  protected function parseLimit($orm)
  {
    if ($this->limit == null) {
      return $orm;
    }
    $orm = $orm->limit($this->limit->limit);
    if (isset($this->limit->offset)) {
      $orm = $orm->offset($this->limit->offset);
    }
    return $orm;
  }
  protected function findAggregator()
  {
    $model = $this->modelName($this->class);
    $ids = $this->getIds($model);
    $class = $this->class;
    if (count($ids) == 0) {
      return false;
    }
    if ($this->many) {
      $objs = [];
      foreach ($ids as $id) {
        $objs []= new $class($id);
      }
    } else {
      $objs = new $class($ids[0]);
    }
    return $objs;
  }
  protected function getIds($model)
  {
    $id = $this->getModelId($model);
    $table = $this->getTable($model);
    $st = \ORM::forTable($table)->select($id);
    $st = $this->parseConditions($st);
    $results = $st->findArray();
    $output = array_map(function($a) use($id) {
      return $a[$id];
    }, $results);
    return $output;
  }
  protected function modelName($class)
  {
    $arr = explode("\\", $class);
    \array_push($arr, end($arr));
    return implode("\\", $arr);
  }
  protected function getModelId($model)
  {
    $table = $this->getTable($model);
    $query = "SHOW KEYS FROM {$table} WHERE Key_name = 'PRIMARY'";
    $st = \ORM::getDb()->query($query);
    $results = $st->fetchAll(\PDO::FETCH_OBJ);
    return $results[0]->Column_name;
  }
  protected function getTable($model)
  {
    $arr = explode("\\", $model);
    return Stringy::create(end($arr))->toLowerCase() . '';
  }
}
