<?php



namespace QuickBooksOnline\API\Diagnostics;



use QuickBooksOnline\API\Exception\SdkException;



/**

 * Writes temporary file

 *

 * @author amatiushkin

 */

class ContentWriter

{



    /**

     * Contains content to write

     * @var mixed (string or binary)

     */

    private $content = null;



    /**

     * Prefix which will be added at the beginning of a filename.

     * @see setPrefix() for details

     * @var string

     */

    private $prefix = null;



    /**

     * Number of bytes in content

     * @var int

     */

    private $bytes = null;



    /**

     * Full path including filename for created temporary file

     * @var string

     */

    private $tempPath = null;



    /**

     * Contains reference to resource (if applicable)

     * @var resource

     */

    private $handler = null;



    public function __construct($content = null)

    {

        $this->content = $content;

    }



    /**

     * Set prefix for a temporary filename.

     * It is used only by saveTemp()

     * @param string $prefix

     *

     * @return $this

     */

    public function setPrefix($prefix)

    {

        $this->prefix = $prefix;

        return $this;

    }



    /**

     * Returns prefix

     * @return string

     */

    public function getPrefix()

    {

        return $this->prefix;

    }



    /**

     * Returns content

     * @return mixed (string or binary)

     */

    public function getContent()

    {

        return $this->content;

    }



    /**

     * Deletes content, however everything else remains available

     * This method is intented to use for better memory management

     */

    public function resetContent()

    {

        $this->content = null;

    }



    /**

     * Returns unique filename with prefix (if applicable)

     * note: This method is not intented to generate unpredictable strings,

     * even with very low likelihood it may happen with garbaged tmp folder.

     * Suggestion is to clean up php temp folder (which is usually sys temp folder).

     * @return string

     */

    protected function getUniqId()

    {

        return uniqid($this->getPrefix(), true);

    }



    /**

     * Creates temporary file in system temporary folder.

     * It returns path to the file

     * @return string path

     */

    private function createTempFile()

    {

        return tempnam(sys_get_temp_dir(), $this->getUniqId());

    }



    /**

     * Returns path to temporary file

     * @return string

     */

    public function getTempPath()

    {

        return $this->tempPath;

    }



    /**

     * Returns number of bytes, which were written

     * @return int

     */

    public function getBytes()

    {

        return $this->bytes;

    }



    /**

     * Returns true if this writer instance refers to file by stream reference

     * @return bool

     */

    public function isHandler()

    {

        return !is_null($this->getHandler())

               && is_resource($this->getHandler());

    }



    /**

     * Returns file handler to temporary file

     * It is the same kind as ones, which are returned by @see fopen()

     * @return resource

     */

    public function getHandler()

    {

        return $this->handler;

    }



    /**

     * Writes content into temporary file. It always creates new temp file.

     * Use getTempPath() to retrieve actual path.

     * This method doesn't delete the file after script termination.

     * @return boolean

     * @throws SdkException

     */

    public function saveTemp()

    {

        $this->tempPath = $this->createTempFile();

        if (is_writable($this->getTempPath())) {

            $result = file_put_contents($this->getTempPath(), $this->getContent());

            if (false === $result) {

                throw new SdkException('Unable to put content into temp file: ' . $this->getTempPath());

            }

            if (empty($result)) {

                throw new SdkException('Empty or zero file was received: ' . $this->getTempPath());

            }

            $this->bytes = $result;

            return true;

        }



        throw new SdkException('Unable to write temp file: ' . $this->tempPath);

    }



    /**

     * Writes content into temporary file and returns open handler to it.

     * The temporary file is removed when closed (e.g. by calling fclose()) or

     * there are no more references (e.g. variables) or with script termination.

     * @throws SdkException

     */

    public function saveAsHandler()

    {

        $this->handler = tmpfile();

        if (false === $this->handler) {

            throw new SdkException('Unable to create a handler for tempfile in ' . sys_get_temp_dir());

        }

        if (empty($this->handler)) {

            throw new SdkException('Handler has an invalid state. It is empty.');

        }

        $this->tempPath = $this->getPathFromHandler();

        if (empty($this->tempPath)) {

            throw new SdkException('Unable to locate path from stream source');

        }

        $result = fwrite($this->handler, $this->getContent());

        if (false === $result) {

            throw new SdkException('Unable to write content into temp file: ' . $this->getTempPath());

        }

        if (empty($result)) {

            throw new SdkException('Empty or zero file was received: ' . $this->getTempPath());

        }

        if (-1 === fseek($this->handler, 0)) {

            throw new SdkException('Unable to reset file pointer in the file ' . $this->getTempPath());

        }

        $this->bytes = $result;

    }



    /**

     * Moves file from temp location to specified folder and name

     * @param string $dir

     * @param string|null $name (option) if not specified it will be moved with temp name

     * @throws SdkException

     */

    public function saveFile($dir, $name=null)

    {

        if (empty($dir)) {

            throw new SdkException("Directory is empty.");

        }



        if (!file_exists($dir)) {

            throw new SdkException("Directory ($dir) doesn't exist.");

        }



        if (!is_writable($dir)) {

            throw new SdkException("Directory ($dir) is not writable");

        }

        $this->tempPath = $dir . DIRECTORY_SEPARATOR . $this->generateFileName($name);

        if (file_exists($this->tempPath)) {

            throw new SdkException("File ($this->tempPath) already exists");

        }

        try {

            $result = file_put_contents($this->tempPath, $this->getContent());

        } catch (\Exception $e) {

            if (!is_writable($this->tempPath)) {

                throw new SdkException("File ({$this->tempPath}) is not writable");

            }

            throw new SdkException("Error was thrown: " . $e->getMessage() . "\File: " .$e->getFile() ." on line " . $e->getLine());

        }

        if (false === $result) {

            throw new SdkException('Unable to write content into temp file: ' . $this->tempPath);

        }

        $this->bytes = $result;

    }



    /**

     * Creates filename based on name and prefix.

     * It generates uniqid-like string if name is ommited

     * @param string $name

     * @return string

     */

    private function generateFileName($name)

    {

        $filename = is_null($name) ? $this->getUniqId() : $name;

        return is_null($this->getPrefix()) ? $filename : $this->getPrefix() . $filename;

    }





    /**

     * Returns actual path from actual file handler

     * @return string

     */

    private function getPathFromHandler()

    {

        $metaDatas = stream_get_meta_data($this->handler);

        if (array_key_exists('uri', $metaDatas)) {

            return $metaDatas['uri'];

        }

        return null;

    }

}

