<?php
/**
 * Class which implement a simple Iterator over a PDO connexion
 *
 * PHP version 5
 *
 * Copyright © 2010 Remi Collet
 *
 * This file is part of rpmphp.
 *
 * rpmphp is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * rpmphp 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with rpmphp.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  Main
 * @package   RPMPHP
 *
 * @author    Remi Collet <unknown@unknwown.com>
 * @author    Johan Cwiklinski <johan@x-tnd.be>
 * @copyright 2010 Remi Collet
 * @license   http://www.gnu.org/licenses/agpl-3.0-standalone.html AGPL License 3.0 or (at your option) any later version
 * @link      http://github.com/remicollet/rpmphp/
 * @since     The begining of times.
*/

/**
 * Helper for simple query => use directly or through  CommonTable::request()
 *
 * Freely inspired from DBmysqlIterator class from GLPI
 * (already written by Remi, and ported to PDO)
 * See http://www.glpi-project.org/
 *
 * @category Main
 * @package  RPMPHP
 *
 * @author   Remi Collet <unknown@unknwown.com>
 * @author   Johan Cwiklinski <johan@x-tnd.be>
 * @license  http://www.gnu.org/licenses/agpl-3.0-standalone.html AGPL License 3.0 or (at your option) any later version
 * @link     http://github.com/remicollet/rpmphp/
 */
class TableIterator  implements Iterator
{
    private $_con;
    private $_sql;
    private $_res = false;
    private $_row;
    private $_pos;

    /**
     * Constructor
     *
     * @param CommonDBTM   $dbconnexion Database Connnexion
     *                                  (must be a CommonDBTM object)
     * @param string       $table       table name or complete SQL request
     * @param string|array $crit        string or array of filed/values,
     *                                  ex array("id"=>1), if empty => all rows
     */
    function __construct (PDO $dbconnexion, $table, $crit='')
    {

        $this->_conn = $dbconnexion;
        if (is_string($table) && strpos($table, " ")) {
            $this->_sql = $table;
        } else {
            // check field, orderby, limit, start in criterias
            $field="";
            $orderby="";
            $limit=0;
            $start=0;

            if (is_array($crit) && count($crit)) {
                foreach ($crit as $key => $val) {
                    if ($key==="FIELDS") {
                        $field = $val;
                        unset($crit[$key]);
                    } else if ($key==="ORDER") {
                        $orderby = $val;
                        unset($crit[$key]);
                    } else if ($key==="LIMIT") {
                        $limit = $val;
                        unset($crit[$key]);
                    } else if ($key==="START") {
                            $start = $val;
                            unset($crit[$key]);
                    }
                }
            }

            // SELECT field list
            if (is_array($field)) {
                $this->_sql = "";
                foreach ($field as $t => $f) {
                    if (is_numeric($t)) {
                        $this->_sql .= (empty($this->_sql)
                            ? "SELECT " : ",") . $f;
                    } else if (is_array($f)) {
                        $this->_sql .= (empty($this->_sql)
                            ? "SELECT $t." : ",$t.") . implode(",$t.", $f);
                    } else {
                        $this->_sql .= (empty($this->_sql)
                            ? "SELECT " : ",") . "$t.$f";
                    }
                }
            } else if (empty($field)) {
                $this->_sql = "SELECT *";
            } else {
                $this->_sql = "SELECT `$field`";
            }
            // FROM table list
            if (is_array($table)) {
                $this->_sql .= " FROM `".implode("`, `", $table)."`";
            } else {
                $this->_sql .= " FROM `$table`";
            }
            // WHERE criteria list
            if (!empty($crit)) {
                $this->_sql .= " WHERE ".$this->_analyseCrit($crit);
            }
            // ORDER BY
            if (is_array($orderby)) {
                $this->_sql .= " ORDER BY `".implode("`, `", $orderby)."`";
            } else if (!empty($orderby)) {
                $this->_sql .= " ORDER BY `$orderby`";
            }
            if (is_numeric($limit) && $limit>0) {
                $this->_sql .= " LIMIT $limit";
                if (is_numeric($start) && $start>0) {
                    $this->_sql .= " OFFSET $start";
                }
            }
        }
        //echo "SQL: ".$this->_sql."\n";
        $this->_res = $this->_conn->prepare($this->_sql);
        if ($this->_res===false) {
            $err = $this->db->errorInfo();
            throw new Exception($err[2]);
        }

        $this->_pos = -1;
    }

    /**
     * Class destructor
     */
    function __destruct ()
    {
        if ($this->_res) {
            $this->_res->closeCursor();
        }
    }

    /**
     * Build WHERE clause
     *
     * @param string|array $crit To document
     * @param string       $bool logical operator between criteria
     *
     * @return To document
     */
    private function _analyseCrit ($crit, $bool="AND")
    {

        if (!is_array($crit)) {
            return $crit;
        }
        $ret = "";
        foreach ($crit as $name => $value) {
            if (!empty($ret)) {
                $ret .= " $bool ";
            }
            if (is_numeric($name)) {
                // No Key case => recurse.
                $ret .= "(" . $this->_analyseCrit($value, $bool) . ")";
            } else if ($name==="OR" || $name==="AND") {
                // Binary logical operator
                $ret .= "(" . $this->_analyseCrit($value, $name) . ")";
            } else if ($name==="NOT") {
                // Uninary logicial operator
                $ret .= " NOT (" . $this->_analyseCrit($value, "AND") . ")";
            } else if ($name==="FKEY") {
                // Foreign Key condition
                if (is_array($value) && count($value)==2) {
                    reset($value);
                    list($t1,$f1)=each($value);
                    list($t2,$f2)=each($value);
                    $ret .= (is_numeric($t1) ? "$f1" : "$t1.$f1") . "=" .
                            (is_numeric($t2) ? "$f2" : "$t2.$f2");
                } else {
                    trigger_error("BAD FOREIGN KEY", E_USER_ERROR);
                }
            } else if (is_array($value)) {
                // Array of Value
                $ret .= "$name IN ('". implode("','", $value)."')";
            } else if (is_null($value)) {
                // NULL condition
                $ret .= "$name IS NULL";
            } else if (is_numeric($value)) {
                // Integer
                $ret .= "$name=$value";
            } else {
                // String
                $ret .= "$name='$value'";
            }
        }
        return $ret;
    }

    /**
     * Go to the begin of the request (launch it)
     *
     * @return hastable|false : next row
    */
    public function rewind ()
    {

        if ($this->_res && $this->_pos>=0) {
            $this->_res->closeCursor();
            $this->_pos = -1;
        }

        if ($this->_res && $this->_pos<0) {
            if (!$this->_res->execute()) {
                $err = $this->_res->errorInfo();
                throw new Exception($err[2]);
            }
        }
        return $this->next();
    }

    /**
     * Retrieve current row
     *
     * @return hastable|false
     */
    public function current()
    {
        return $this->_row;
    }

    /**
     * Retrieve key of current row
     *
     * @return mixed
     */
    public function key()
    {
        return (isset($this->_row["id"]) ? $this->_row["id"] : $this->_pos);
    }

    /**
     * Retrieve next row
     *
     * @return hastable|false
     */
    public function next()
    {
        if (!$this->_res) {
            return false;
        }
        $this->_row = $this->_res->fetch(PDO::FETCH_ASSOC);
        $this->_pos++;
        return $this->_row;
    }

    /**
     * Is current row defined
     *
     * @return boolean
     */
    public function valid()
    {
        return $this->_res && $this->_row;
    }

    /**
     * Compute number of rows
     *
     * @return integer
     */
    public function numrows()
    {
        return ($this->_res ? $this->_res->rowCount() : 0);
    }
}
?>