<?php
/**
 * Underscore Zero
 *
 * a collection of useful tools
 * for PHP... and CMSMS...
 */

namespace CMSMSExt\underscore_zero
{
  // Returns an instance of _0_ for OOP-style calls
  use CMSMSExt\core\fifo;
  use CMSMSExt\core\queue;
  use CMSMSExt\core\stack;
  use CMSMSExt\core\xt_registry as registry;
  
  function _0_($item = NULL)
  {
    $_0_ = new _0_;
    if(\func_num_args() > 0) { $_0_->_wrapped = $item; }
    
    return _0_;
  }
  
  // Underscore Zero
  
  
  /**
   *
   */
  class _0_
  {
    // Singleton
    private static $_instance;
    
    # internal
    private static stack $_stack;
    private static queue $_queue;
    private static fifo $_fifo;
    private static registry $_registry;
    // Start the chain
    private bool $_chained = FALSE; // Are we in a chain?
    // Extend the class with your own functions
    private $_mixins = [];
    
    public $_wrapped; // Value passed from one chained method to the next
    
    
    public function __construct($target = NULL, $callable = NULL)
    {
      self::$_stack = new stack;
      self::$_queue = new queue;
      self::$_fifo  = new fifo;
      self::$_registry = registry::getInstance();
    }
    
    public static function getInstance()
    {
      if(!isset(self::$_instance))
      {
        $c               = __CLASS__;
        self::$_instance = new $c;
      }
      
      return self::$_instance;
    }
    
    
    /**
     * Calls a static method or mixin by name and passes the provided arguments.
     * Allows extending methods in static context
     *
     * @note methods prefixed with an underscore are treated as private
     * @param string $name The name of the method or mixin to call.
     * @param array $arguments The arguments to pass to the method or mixin.
     * @throws \BadMethodCallException If the method or mixin does not exist.
     *
     * @return mixed The result of the method or mixin call.
     */
    public static function __callStatic($name, $arguments)
    {
      if( !\startswith($name,  '_') && \method_exists(__CLASS__, $name) )
      {
        $c = self::getInstance();
        return \call_user_func_array([$c, $name], $arguments);
      }
      
      $mixins = &self::getInstance()->_mixins;
      
      if( !isset($mixins[$name]) )
      {
        throw new \BadMethodCallException($name);
      }
      
      return \call_user_func_array($mixins[$name], $arguments);
    }
    
    
    
    /**
     * Executes inaccessible methods or mixins dynamically based on the given name and arguments.
     *
     * @note methods prefixed with an underscore are treated as private
     * @param string $name The name of the method or mixin to execute.
     * @param array $arguments The arguments to pass to the method or mixin.
     * @throws \BadMethodCallException If the method does not exist or is not accessible.
     * @return mixed The result of the method execution.
     */
    public function __call($name, $arguments)
    {
      if( !\startswith($name,  '_') && \method_exists($this, $name) )
      {
        return \call_user_func_array([$this, $name], $arguments);
      }
      
      $mixins    = &self::getInstance()->_mixins;
      
      if( !isset($mixins[$name]) )
      {
        throw new \BadMethodCallException($name);
      }
      
      $arguments = self::_wrapArgs($arguments);
      
      return \call_user_func_array($mixins[$name], $arguments);
    }
    
    /**
     * Wraps a value and returns it.
     *
     * @note  All methods should wrap their returns within _wrap,
     *        because this function understands both OO-style
     *        and functional  calls
     *
     * @param mixed $val The value to be wrapped.
     *
     * @return mixed The wrapped value.
     */
    private function _wrap($val)
    {
      if(isset($this, self::$_chained) && self::$_chained)
      {
        $this->_wrapped = $val;
        
        return $this;
      }
      
      return $val;
    }
    
    
    /**
     * Wraps the arguments passed to the function.
     *
     * methods should get their arguments from _wrapArgs
     * because this function understands both OO-style and functional calls
     *
     * @param array    $caller_args The arguments passed to the function.
     * @param int|null $num_args The number of arguments to wrap.
     *                           Defaults to the total number of caller arguments minus 1.
     *
     * @return array The wrapped arguments.
     */
    private function _wrapArgs($caller_args, $num_args = NULL)
    {
      $num_args = $num_args ?? (\count($caller_args) - 1);
      
      $filled_args = [];
      
      if( isset($this, $this->_wrapped) )
      {
        $filled_args[] =& $this->_wrapped;
      }
      
      if(\count($caller_args) > 0)
      {
        foreach($caller_args as $k => $v) { $filled_args[] = $v; }
      }
      
      return \array_pad($filled_args, $num_args, NULL);
    }
    
    /**
     * Chains a new item to the existing dbg object.
     *
     * @param mixed $item The item to chain.
     * @return dbg The dbg object with the new item chained.
     */
    private function chain($item = NULL) : dbg
    {
      [$item] = self::_wrapArgs(\func_get_args(), 1);
      
      $dbg           = (isset($this, self::$_chained) && self::$_chained) ? $this : dbg($item);
      $dbg->_chained = TRUE;
      
      return $dbg;
    }
    
    
    /**
     * End the chain and return the value.
     *
     * @return null
     */
    private function value()
    {
      return ( isset($this) ) ? $this->_wrapped : NULL;
    }
    
    /**
     * Mixes in the given functions into the class's list of mixins.
     *
     * @param mixed $functions The functions to be mixed in.
     *
     * @return mixed The result of the _wrap method.
     */
    private function mixin($functions = NULL)
    {
      [$functions] = self::_wrapArgs(\func_get_args(), 1);
      
      $mixins = &self::getInstance()->_mixins;
      foreach($functions as $name => $function)
      {
        $mixins[$name] = $function;
      }
      
      return self::_wrap(NULL);
    }
    

    
    /**
     * Internal Stack
     */
    
    private function _st_reset()
    {
      self::$_stack->reset();
    }
    
    /**
     * @param $item
     *
     * @return void
     */
    private function _st_push($item = NULL)
    {
      if( NULL !== $item)
      {
        self::$_stack->push($item);
      }
    }
  
    /**
     * @return mixed|null
     */
    private function _st_pop()
    {
      return self::$_stack->pop();
    }
  
    /**
     * @return false|mixed
     */
    private function _st_peek()
    {
      return self::$_stack->peek();
    }
    
    private function _st_count()
    {
      return self::$_stack->count();
    }
    
    private function stack_reset()
    {
      $this->_st_reset();
      return $this;
    }
    
    private function stack_count()
    {
      return $this->_st_count();
    }
    
    private function stack_peek()
    {
      return $this->_st_peek();
    }
  
    /**
     *
     * @param $item
     *
     * @return $this
     */
    private function stack_push($item = NULL)
    {
      [$item] = self::_wrapArgs(\func_get_args(), 1);
      $this->_st_push($item);
      
      return self::_wrap($this);
    }
  
    private function stack_pop()
    {
      return $this->_st_pop();
    }
    
    /**
     * Internal Queue
     */
    
    private function _q_reset()
    {
      self::$_queue->reset();
      
      return $this;
    }
    
    /**
     * @param $item
     *
     * @return void
     */
    private function _q_enqueue($item = NULL)
    {
      if( NULL !== $item)
      {
        self::$_queue->enqueue($item);
      }
    }
  
    /**
     * @return mixed|null
     */
    private function _q_dequeue()
    {
      return self::$_queue->dequeue();
    }
  
    /**
     * @return false|mixed
     */
    private function _q_peek()
    {
      return self::$_queue->peek();
    }
    
    private function _q_count()
    {
      return self::$_queue->size();
    }
    
    private function queue_reset()
    {
      $this->_q_reset();
      
      return self::_wrap($this);
    }
    
    private function queue_count()
    {
      return $this->_q_count();
    }
    
    private function queue_peek()
    {
      return $this->_q_peek();
    }
  
    private function queue_enqueue($item = NULL)
    {
      [$item] = self::_wrapArgs(\func_get_args(), 1);
      $this->_q_enqueue($item);
      
      return self::_wrap($this);
    }
  
    private function queue_dequeue()
    {
      return self::_wrap( $this->_q_dequeue() );
    }
    
    private function _fifo_reset()
    {
      self::$_fifo->reset();
    }
    
    private function _fifo_enqueue($item = NULL)
    {
      [$item] = self::_wrapArgs(\func_get_args(), 1);
      
      if( NULL !== $item)
      {
        self::$_fifo->enqueue($item);
      }
      
      return self::_wrap($this);
    }
    
    private function _fifo_dequeue()
    {
      return self::$_fifo->dequeue();
    }
    
    private function _fifo_peek()
    {
      return self::$_fifo->peek();
    }
    
    private function fifo_reset()
    {
      $this->_fifo_reset();
      
      return self::_wrap($this);
    }
    
    private function fifo_enqueue($item = NULL)
    {
      [$item] = self::_wrapArgs(\func_get_args(), 1);
      $this->_fifo_enqueue($item);
      
      return self::_wrap($this);
    }
    
    private function fifo_dequeue()
    {
      return self::_wrap( $this->_fifo_dequeue() );
    }
    
    private function fifo_peek()
    {
      return self::_wrap( $this->_fifo_peek() );
    }
    
    
  }
}

namespace
{
  
  /**
   * imports the lib into the global namespace
   */
  
  /**
   * this allows to call as a function using either
   * _0_()->function($target);
   * or
   * _0_($target)->function();
   */
  if( !\function_exists('_0_') )
  {
    function _0_($item = NULL)
    {
      return \CMSMSExt\underscore_zero\_0_($item);
    }
  }
  
  /**
   * this allows static use
   * _0_::function($target);
   */
  if( !\class_exists('_0_') )
  {
    class _0_ extends \CMSMSExt\underscore_zero\_0_ {}
  }
}

