<?php


namespace CMSMSExt\core;


class morphable extends \ArrayObject
{
  
  /**
   * @param array $arr
   *
   * @return bool
   */
  protected function _isAssoc(array $arr)
  {
    if([] === $arr) { return FALSE; }
    
    return \array_keys($arr) !== \range(0, \count($arr) - 1);
  }
  
  /**
   * @return array
   */
  protected function _to_array()  { return (array)$this; }
  
  /**
   * @param array $array
   *
   * @return $this
   */
  protected function _from_array($array = []) : self
  {
    foreach($array as $k => $v) { $this->{$k} = $v; }
    
    return $this;
  }
  
  /**
   * morphable constructor.
   *
   * @param array  $input
   * @param int    $flags
   * @param string $iterator_class
   */
  public function __construct($input = [], $flags = 0, $iterator_class = 'ArrayIterator')
  {
    $flags = $flags ?: \ArrayObject::ARRAY_AS_PROPS | \ArrayObject::STD_PROP_LIST;
    
    parent::__construct($input, $flags, $iterator_class);
  }

  
  /**
   * A hack with a few purposes:
   * 1 - to be able to use php native functions with the object
   *     example: $AnObject->array_keys();
   *     (note: array_<functions> natively require the array itself as a parameter
   *            in this case the array is object itself so there is no parameter for the array)
   * 2 - to be able to use object methods as setters and getters
   *     example 1:  $AnObject->key($value) (returns the object to be able to chain)
   *     example 2:  $AnObject->key() (returns the value)
   *
   * @param $func
   * @param $argv
   *
   * @return mixed
   */
  public function __call($func, $argv)
  {
    // Check if $func is not a callable or doesn't start with 'array_'
    if(!\is_callable($func) || 0 !== \strpos($func, 'array_'))
    {
      // If arguments are provided, set or return values
      if(!empty($argv))
      {
        // Check if $argv is associative or has more than one element
        if($this->_isAssoc($argv) || \count($argv) > 1)
        {
          $this($argv);
        }
        else
        {
          $this->{$func} = $argv[0];
        }
        
        return $this;
      }
      
      return $this->{$func};
    }
    
    // Call native array function with object data
    return \call_user_func_array($func, \array_merge([$this->getArrayCopy()], $argv));
  }

  
  /**
   * @param string $serialized
   */
  public function __unserialize($serialized) : void
  {
    $this->_from_array( (array)\unserialize($serialized) );
  }
  
  /**
   * @return array
   */
  public function __serialize() : array
  {
    return $this->_to_array();
  }
  
  /**
   * @return string
   * @throws \JsonException
   */
  public function __toString()
  {
    return (string)\json_encode($this->_to_array(), \JSON_THROW_ON_ERROR);
  }
  
  /**
   * A hack with a few purposes:
   * - to be able to set keys and values for our array like object;
   * - if the call is $AnArrayObject('an_argument') it will set a key 'an_argument' with a null value;
   * - if the call is $AnArrayObject('an_argument', 'another_argument') it will set the arguments as keys with a null value;
   * - if the call is $AnArrayObject(['a_key' => 'a_value']) it will set the arguments as key => value pairs;
   *
   * The arguments can be mixed
   *
   * @param array $arguments
   *
   * @return mixed
   */
  public function __invoke(...$arguments)
  {
    foreach($arguments as $argument)
    {
      if(\is_array($argument) && $this->_isAssoc($argument))
      {
        $this->exchangeArray($argument);
      }
      else
      {
        $this->{$argument} = NULL;
      }
    }
    
    return $this;
  }

  
  /**
   * @return array
   */
  public function __debugInfo() : array
  {
    return $this->_to_array();
  }
  
  
  public function FromStdClass(\stdClass $obj)
  {
    $this->_from_array( (array)$obj );
    
    return $this;
  }
  
  public function ToStdClass()
  {
    return (object)$this->_to_array();
  }
  
  public function FromArray(array $array = [])
  {
    $this->_from_array($array);
  }
  
  public function ToArray()
  {
    return $this->_to_array();
  }
  
}

?>