<?php
/**
 * @copyright NetMonsters <team@netmonsters.ru>
 * @link http://netmonsters.ru
 * @package Majestic
 * @subpackage db
 * @since 2011-11-15
 */

class MongoCommandBuilder
{

    const FIND = 'Find';

    const COUNT = 'Count';

    const INSERT = 'Insert';

    const UPDATE = 'Update';

    const REMOVE = 'Remove';

    const COMMAND = 'Command';


    /**
     * @param string $type
     * @param MongoCollection $collection
     * @return MongoDbCommand
     */
    static public function factory($type, $collection = null)
    {
        $class = ucfirst($type) . 'MongoCommand';
        $command = new $class();
        $command->setCollection($collection);
        return $command;
    }
}

abstract class MongoDbCommand
{

    /**
     * @var MongoCollection
     */
    protected $collection;

    /**
     * Execute Mongo command/query
     *
     * @return mixed
     * @throws GeneralException
     */
    public function execute()
    {
        if ($this->checkParams()) {
            return $this->concreteExecute();
        } else {
            throw new GeneralException(get_called_class() . ' error. Bind all required params first.');
        }
    }


    /**
     * @param string $name Parameter name
     * @param mixed $value Parameter value
     * @return MongoDbCommand
     */
    public function bindParam($name, $value)
    {
        if (property_exists($this, $name)) {
            $this->$name = $value;
        }
        return $this;
    }

    /**
     * @param MongoCollection $collection Mongo collection
     * @return MongoDbCommand
     */
    public function setCollection($collection)
    {
        $this->collection = $collection;
        return $this;
    }

    /**
     * Convert query parameters array to string
     *
     * @param array $array
     * @return string
     */
    protected function arrayToString($array)
    {
        $string =  '[';
        if(!empty($array)) {
            $string .= PHP_EOL;
        }
        foreach($array as $key => $value) {
            $string .= "\t" . $key . ' = ' . $value . PHP_EOL;
        }
        $string .= ']' . PHP_EOL;
        return $string;
    }

    abstract protected function concreteExecute();

    abstract protected function checkParams();
}

class FindMongoCommand extends MongoDbCommand
{
    protected $condition = array();

    protected $fields = array();

    protected $multiple = true;

    protected function concreteExecute()
    {
        if ($this->multiple) {
            return $this->collection->find($this->condition, $this->fields);
        } else {
            return $this->collection->findOne($this->condition, $this->fields);
        }
    }

    protected function checkParams()
    {
        if (isset($this->collection) && isset($this->condition) && isset($this->fields)) {
            return true;
        } else {
            return false;
        }
    }

    public function __toString()
    {
        if ($this->checkParams()) {
            $result = PHP_EOL . 'Collection: ' . trim($this->collection, "\n") . PHP_EOL;
            $result .= 'Condition: '  . $this->arrayToString($this->condition);
            $result .= 'Type: FIND' . PHP_EOL;
            $result .= 'Fields: ' . $this->arrayToString($this->fields);
            $mult = $this->multiple ? 'TRUE' : 'FALSE';
            $result .= 'Multiple fields: ' . $mult . PHP_EOL;
            return $result;
        } else {
            return 'Command properties not set';
        }
    }
}

class CountMongoCommand extends MongoDbCommand
{
    protected $condition = array();

    protected $limit = 0;

    protected $skip = 0;

    protected $multiple = true;

    protected function concreteExecute()
    {
        return $this->collection->count($this->condition, $this->limit, $this->skip);
    }

    protected function checkParams()
    {
        if (isset($this->collection) && isset($this->condition) && isset($this->limit) && isset($this->skip)) {
            return true;
        } else {
            return false;
        }
    }

    public function __toString()
    {
        if ($this->checkParams()) {
            $result = PHP_EOL . 'Collection: ' . trim($this->collection, "\n") . PHP_EOL;
            $result .= 'Type: COUNT' . PHP_EOL;
            $result .= 'Condition: ' . $this->arrayToString($this->condition);
            $result .= 'Limit: ' . $this->limit . PHP_EOL;
            $result .= 'Skip: ' . $this->skip . PHP_EOL;
            return $result;
        } else {
            return 'Command properties not set';
        }
    }
}

class InsertMongoCommand extends MongoDbCommand
{
    protected $data;

    protected $safe = true;

    protected $insertId = false;

    protected $multiple = false;

    protected function concreteExecute()
    {
        $result = null;
        if (!$this->multiple) {
            $result = $this->collection->insert($this->data, array('safe' => $this->safe));
            $this->insertId = $this->data['_id'];
        } else {
            if (count($this->data)) {
                $result = $this->collection->batchInsert($this->data, array('safe' => $this->safe));
            }
        }
        return $result;
    }

    protected function checkParams()
    {
        if (isset($this->collection) && isset($this->data) && isset($this->safe)) {
            return true;
        } else {
            return false;
        }
    }

    public function getInsertId()
    {
        return $this->insertId;
    }

    public function __toString()
    {
        if ($this->checkParams()) {
            $result = PHP_EOL . 'Collection: ' . trim($this->collection, "\n") . PHP_EOL;
            $result .= 'Type: INSERT' . PHP_EOL;
            $result .= 'Data: ' . $this->arrayToString($this->data);
            $mult = $this->multiple ? 'TRUE' : 'FALSE';
            $result .= 'Bulk insert: ' . $mult . PHP_EOL;
            $safe = $this->safe ? 'TRUE' : 'FALSE';
            $result .= 'Safe operation: ' . $safe . PHP_EOL;
            return $result;
        } else {
            return 'Command properties not set';
        }
    }
}

class UpdateMongoCommand extends MongoDbCommand
{
    protected $condition;

    protected $data;

    protected $multiple = true;

    protected $upsert = false;

    protected $safe = true;

    protected function concreteExecute()
    {
        return $this->collection->update($this->condition, $this->data,
            array('multiple' => $this->multiple, 'upsert' => $this->upsert, 'safe' => $this->safe));
    }

    protected function checkParams()
    {
        if (isset($this->collection) && isset($this->condition) && isset($this->data) &&
                isset($this->upsert) && isset($this->safe)
        ) {
            return true;
        } else {
            return false;
        }
    }

    public function __toString()
    {
        if ($this->checkParams()) {
            $result = PHP_EOL . 'Collection: ' . trim($this->collection, "\n") . PHP_EOL;
            $result .= 'Type: UPDATE' . PHP_EOL;
            $result .= 'Condition: ' . $this->arrayToString($this->condition);
            $result .= 'Data: ' . $this->arrayToString($this->data);
            $mult = $this->multiple ? 'TRUE' : 'FALSE';
            $result .= 'Multiple fields: ' . $mult . PHP_EOL;
            $upsert = $this->upsert ? 'TRUE' : 'FALSE';
            $result .= 'Upsert: ' . $upsert . PHP_EOL;
            $safe = $this->safe ? 'TRUE' : 'FALSE';
            $result .= 'Safe operation: ' . $safe . PHP_EOL;
            return $result;
        } else {
            return 'Command properties not set';
        }
    }
}

class RemoveMongoCommand extends MongoDbCommand
{
    protected $condition;

    protected $safe = true;

    protected function concreteExecute()
    {
        return $this->collection->remove($this->condition, array('safe' => $this->safe));
    }

    protected function checkParams()
    {
        if (isset($this->collection) && isset($this->condition) && isset($this->safe)) {
            return true;
        } else {
            return false;
        }
    }

    public function __toString()
    {
        if ($this->checkParams()) {
            $result = PHP_EOL . 'Collection: ' . trim($this->collection, "\n") . PHP_EOL;
            $result .= 'Type: REMOVE' . PHP_EOL;
            $result .= 'Condition: ' . $this->arrayToString($this->condition);
            $safe = $this->safe ? 'TRUE' : 'FALSE';
            $result .= 'Safe operation: ' . $safe . PHP_EOL;
            return $result;
        } else {
            return 'Command properties not set';
        }
    }
}

class CommandMongoCommand extends MongoDbCommand
{
    protected $command;

    protected function concreteExecute()
    {
        $db = $this->collection->db;
        if ($db instanceof MongoDB) {
            return $db->command($this->command);
        } else {
            return false;
        }
    }

    protected function checkParams()
    {
        if (isset($this->collection) && isset($this->command)) {
            return true;
        } else {
            return false;
        }
    }

    public function __toString()
    {
        if ($this->checkParams()) {
            $result = PHP_EOL . 'Collection: ' . trim($this->collection, "\n") . PHP_EOL;
            $result .= 'Type: COMMAND' . PHP_EOL;
            $result .= 'Command: ' . $this->arrayToString($this->command);
            return $result;
        } else {
            return 'Command properties not set';
        }
    }
}