<?php

/**
 * CGBetterForms API
 *
 * @package CGBetterUploads
 */

namespace CGBetterUploads;

/**
 * A class to represent an uploaded file.
 * This is an immutable class.
 *
 * @property-read string $guid A unique, randomly generated identifier.
 * @property-read string $category_guid The guid of the category that this file is associated with.
 * @property-read string $filename The original name of the uploaded file.
 * @property-read string $display_name A name to use when displaying this file (usually the same as the file name).
 * @property-read string $author An optional string to identify the uploader.
 * @property-read string $summary A brief description of this file.  In text.
 * @property-read string $description Longer, formatted text for htis file.
 * @property-read int $created The unix timestamp of the time when this record was created.
 * @property-read int $size the size of the uploaded file
 * @property-read array|null $fields An associative array of field names and values.  May be null.
 */
class Upload implements UploadReadInterface, UploadWriteInterface
{
    /**
     * @ignore
     */
    private $_data = [ 'id'=>null, 'guid'=>null, 'category_guid'=>null, 'filename'=>null, 'display_name'=>null, 'author'=>null, 'summary'=>null,
                       'description'=>null, 'created'=>null, 'size'=>null, 'extra'=>null ];

    /**
     * @ignore
     */
    private $_extra;

    /**
     * @ignore
     */
    private $_fields;

    /**
     * Constructor
     *
     * @param array $in An array of properties and values to construct the object with.
     */
    public function __construct(array $in = null)
    {
        foreach( $in as $key => $val ) {
            switch( $key ) {
            case 'guid':
            case 'category_guid':
            case 'filename':
            case 'display_name':
            case 'author':
            case 'summary':
            case 'description':
                $this->_data[$key] = trim($val);
                break;
            case 'id':
            case 'created':
            case 'size':
                $this->_data[$key] = (int) $val;
                break;
            case 'extra':
                $tmp = json_decode( $val, TRUE );
                if( $tmp ) $this->_extra = $tmp;
                break;
            case 'fields':
                if( is_array($val) && isset($val[0]) && isset($val[0]['field_id']) && $val[0]['field_id'] > 0 ) {
                    $arr = null;
                    foreach( $val as $row ) {
                        $arr[$row['field_id']] = $row['value'];
                    }
                    $this->_fields = $arr;
                }
                else if( is_string($val)) {
                    $tmp = json_decode( $val, TRUE );
                    if( $tmp ) $this->_fields = $tmp;
                }
                break;
            }
        }
    }

    /**
     * @ignore
     */
    public function __get(string $key)
    {
        return $this->get($key);
    }

    /**
     * @ignore
     */
    public function __set( string $key, $val )
    {
        throw new \LogicException("$key is not a settable property of ".__CLASS__);
    }

    /**
     * Get a property value.
     * See the properties of this object for valid values for $key
     *
     * @param string $key
     * @return mixed
     */
    public function get(string $key)
    {
        switch( $key ) {
        case 'guid':
        case 'category_guid':
        case 'filename':
        case 'display_name':
        case 'author':
        case 'summary':
        case 'description':
            if( !is_null($this->_data[$key]) ) return trim($this->_data[$key]);
            return;

        case 'id':  // note: the id is only used for tying to external modules (like Search) that rely on integer ids.
        case 'created':
        case 'size':
            if( !is_null($this->_data[$key]) ) return (int) $this->_data[$key];
            return;

        case 'fields':
            if( !is_null($this->_fields) ) return $this->_fields;
            return;

        default:
            throw new \LogicException("$key is not a gettable property of ".__CLASS__);
        }
    }

    /**
     * Get the value of a single field, if it exists.
     *
     * @param string $key The field name
     * @return string|null
     */
    public function fieldVal(string $key)
    {
        $key = trim($key);
        if( isset($this->_fields[$key]) ) return $this->_fields[$key];
    }

    /**
     * @ignore
     */
    protected function set_extra_val(string $key, $val) : Upload
    {
        $obj = clone $this;
        $obj->_extra[$key] = $val;
        return $obj;
    }

    /**
     * @ignore
     */
    public function get_extra_str() : string
    {
        return json_encode( $this->_extra );
    }

    /**
     * @ignore
     */
    protected function get_extra_val(string $key)
    {
        return $this->_extra[$key] ?? null;
    }
    /**
     * Adjust the author of this upload
     *
     * @internal
     * @param string $name The author name
     * @return UploadWriteInterface
     */
    public function withAuthor( string $name ) : UploadWriteInterface
    {
        $obj = clone $this;
        $obj->_data['author'] = trim($name);
        return $obj;
    }

    /**
     * Adjust the size of this upload.
     * Should never be called by external code.
     *
     * @internal
     * @param int $val
     * @return UploadWriteInterface
     */
    public function withSize( int $val ) : UploadWriteInterface
    {
        $obj = clone $this;
        $obj->_data['size'] = max(0,$val);
        return $obj;
    }

    /**
     * Adjust the filename of the upload
     *
     * @internal
     * @param string $name
     * @return UploadWriteInterface
     */
    public function withFilename( string $name ) : UploadWriteInterface
    {
        $obj = clone $this;
        $obj->_data['filename'] = trim($name);
        return $obj;
    }

    /**
     * Adjust the category of this upload.
     * No checks are done to ensure that the category guid is valid, and no files are moved.
     *
     * @param string $guid
     * @return UploadWriteInterface
     */
    public function withCategory( string $guid ) : UploadWriteInterface
    {
        $obj = clone $this;
        $obj->_data['category_guid'] = trim($guid);
        return $obj;
    }

    /**
     * Adjust the summary of this upload.
     *
     * @param string $text
     * @return UploadWriteInterface
     */
    public function withSummary( string $text = null ) : UploadWriteInterface
    {
        $obj = clone $this;
        if( !is_null($text) ) $text = trim($text);
        $obj->_data['summary'] = $text;
        return $obj;
    }

    /**
     * Adjust the display name of this file.
     * No checks are performed to ensure that the data is valid.
     *
     * @param string $text The new display name.
     * @return UploadWriteInterface
     */
    public function withDisplayName( string $text = null ) : UploadWriteInterface
    {
        $obj = clone $this;
        if( !is_null($text) ) $text = trim($text);
        $obj->_data['display_name'] = $text;
        return $obj;
    }

    /**
     * Adjust the formatted description for this file.
     *
     * @param string $text The new display name.
     * @return UploadWriteInterface
     */
    public function withDescription( string $text = null ) : UploadWriteInterface
    {
        $obj = clone $this;
        if( !is_null($text) ) $text = trim($text);
        $obj->_data['description'] = $text;
        return $obj;
    }

    /**
     * set a field value for the upload.
     *
     * @param string $field The field name.
     * @param string $val
     * @return UploadWriteInterface
     */
    public function withField( string $field, string $val ) : UploadWriteInterface
    {
        $field = trim($field);
        $obj = clone $this;
        $val = trim($val);
        if( !$val ) $val = null;
        $obj->_fields[$field] = $val;
        return $obj;
    }

    /**
     * Output this object as an array that can be saved in a database.
     *
     * @internal
     * @return array
     */
    public function as_array() : array
    {
        $out = $this->_data;
        $out['extra'] = ($this->_extra) ? json_encode($this->_extra) : null;
        $out['fields'] = ($this->_fields) ? json_encode($this->fields) : null;
        return $out;
    }

} // class
