<?php
namespace Luracast\Restler\Format;


use Luracast\Restler\Data\Obj;
use Luracast\Restler\RestException;

/**
 * Comma Separated Value Format
 *
 * @category   Framework
 * @package    Restler
 * @subpackage format
 * @author     R.Arul Kumaran <arul@luracast.com>
 * @copyright  2010 Luracast
 * @license    http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @link       http://luracast.com/products/restler/
 *
 */
class CsvFormat extends Format implements iDecodeStream
{

    const MIME = 'text/csv';
    const EXTENSION = 'csv';
    public static $delimiter = ',';
    public static $enclosure = '"';
    public static $escape = '\\';
    public static $haveHeaders = null;

    /**
     * Encode the given data in the csv format
     *
     * @param array   $data
     *            resulting data that needs to
     *            be encoded in the given format
     * @param boolean $humanReadable
     *            set to TRUE when restler
     *            is not running in production mode. Formatter has to
     *            make the encoded output more human readable
     *
     * @return string encoded string
     *
     * @throws RestException 500 on unsupported data
     */
    public function encode($data, $humanReadable = false)
    {
        $char = Obj::$separatorChar;
        Obj::$separatorChar = false;
        $data = Obj::toArray($data);
        Obj::$separatorChar = $char;
        if (is_array($data) && array_values($data) == $data) {
            //if indexed array
            $lines = array();
            $row = array_shift($data);
            if (array_values($row) != $row) {
                $lines[] = static::putRow(array_keys($row));
            }
            $lines[] = static::putRow(array_values($row));
            foreach ($data as $row) {
                $lines[] = static::putRow(array_values($row));
            }
            return implode(PHP_EOL, $lines) . PHP_EOL;
        }
        throw new RestException(
            500,
            'Unsupported data for ' . strtoupper(static::EXTENSION) . ' format'
        );
    }

    protected static function putRow($data)
    {
        $fp = fopen('php://temp', 'r+');
        fputcsv($fp, $data, static::$delimiter, static::$enclosure);
        rewind($fp);
        $data = fread($fp, 1048576);
        fclose($fp);
        return rtrim($data, PHP_EOL);
    }

    /**
     * Decode the given data from the csv format
     *
     * @param string $data
     *            data sent from client to
     *            the api in the given format.
     *
     * @return array associative array of the parsed data
     */
    public function decode($data)
    {
        $decoded = array();

        if (empty($data)) {
            return $decoded;
        }

        $lines = array_filter(explode(PHP_EOL, $data));

        $keys = false;
        $row = static::getRow(array_shift($lines));

        if (is_null(static::$haveHeaders)) {
            //try to guess with the given data
            static::$haveHeaders = !count(array_filter($row, 'is_numeric'));
        }

        static::$haveHeaders ? $keys = $row : $decoded[] = $row;

        while (($row = static::getRow(array_shift($lines), $keys)) !== FALSE)
            $decoded [] = $row;

        $char = Obj::$separatorChar;
        Obj::$separatorChar = false;
        $decoded = Obj::toArray($decoded);
        Obj::$separatorChar = $char;
        return $decoded;
    }

    protected static function getRow($data, $keys = false)
    {
        if (empty($data)) {
            return false;
        }
        $line = str_getcsv(
            $data,
            static::$delimiter,
            static::$enclosure,
            static::$escape
        );

        $row = array();
        foreach ($line as $key => $value) {
            if (is_numeric($value))
                $value = floatval($value);
            if ($keys) {
                if (isset($keys [$key]))
                    $row [$keys [$key]] = $value;
            } else {
                $row [$key] = $value;
            }
        }
        if ($keys) {
            for ($i = count($row); $i < count($keys); $i++) {
                $row[$keys[$i]] = null;
            }
        }
        return $row;
    }

    /**
     * Decode the given data stream
     *
     * @param string $stream A stream resource with data
     *                       sent from client to the api
     *                       in the given format.
     *
     * @return array associative array of the parsed data
     */
    public function decodeStream($stream)
    {
        $decoded = array();

        $keys = false;
        $row = static::getRow(stream_get_line($stream, 0, PHP_EOL));
        if (is_null(static::$haveHeaders)) {
            //try to guess with the given data
            static::$haveHeaders = !count(array_filter($row, 'is_numeric'));
        }

        static::$haveHeaders ? $keys = $row : $decoded[] = $row;

        while (($row = static::getRow(stream_get_line($stream, 0, PHP_EOL), $keys)) !== FALSE)
            $decoded [] = $row;

        $char = Obj::$separatorChar;
        Obj::$separatorChar = false;
        $decoded = Obj::toArray($decoded);
        Obj::$separatorChar = $char;
        return $decoded;
    }
}
