<?php defined('SYSPATH') or die('No direct access allowed.');
/**
 * @package    Asterisk
 * @author     K Anderson <bitbashing@gmail.com>
 * @license    Mozilla Public License (MPL)
 * @created    Oct 5, 2009
 */
class Asterisk extends Telephony_Driver
{
    /**
     * Multi-dimensional array. Key is the filename, which contains an array generated by parse_ini_file
     * @var array
     */
    public $config;

    /**
     * An instance of the AsteriskManager object, allowing configuration remotely via the AMI interface.
     * @var AsteriskManager
     */
    public $ami;

    /**
     * Instance of AsteriskDoc. Keeps a working copy of any relevant loaded/modified Asterisk configuration files in memory
     * for committing later to disk or AMI.
     * @var AsteriskDoc
     */
    public $doc;

    /**
     *
     * @return Asterisk
     */
    public static function getInstance()
    {
        // create a new instance if there isnt one already
        if (!isset(self::$instance))
        {
            self::$instance = new self();

            Kohana::log('debug', 'Asterisk -> New instance of Asterisk telephony driver created.');

            // reset all the accumulators in this driver
            self::reset();
        }

        // return this instance
        return self::$instance;
    }

    /**
     * Loads a context/section from an Asterisk config file.
     * @param array $options Array with keys for filename and context (both are required)
     * @param boolean $useCache OK to use cache if already in memory? Defaults to TRUE
     * @return string String object containing loaded data, if any, or FALSE if failed
     *
     * TODO: Add ability to load an entire file (should just be the built-in php ini options command, but need to check AMI equiv)
     */
    public function load($options, $useCache = TRUE)
    {
        // sanity check on our necessary vars
        if (empty($options['filename']) || empty($options['context']))
        {
            return FALSE;
        } 
        else
        {
            extract($options);
        }

        // CACHE: check if this file's config section is already loaded and if we're allowed to use it
        if ($useCache and isset(self::$instance->doc->fileCache[$filename][$context]))
        {
            // Already loaded
            Kohana::log('debug', 'Asterisk -> Using cached file ' . $filename . ' for context ' . $context);
            
            return self::$instance->doc->fileCache[$filename][$context];
        }

        Kohana::log('debug', 'Asterisk -> Loading file ' . $filename . ' for context ' . $context . ' into cache');

        // get the asterisk manager interface
        $ami = self::$instance->ami;

        // make sure we are connected/can connect
        if (!self::connectAMI($ami)) {
            return FALSE;
        }

        // load the config section and store it in the active in-memory document
        self::$instance->doc->fileCache[$filename][$context] = $ami->loadConfigContext($filename, $context);
        //Kohana::log('debug', 'Loaded ' . $filename . ' / context ' . $context . ":\n" . print_r(self::$instance->doc->fileCache, TRUE));

        return self::$instance->doc->fileCache[$filename][$context];;
    }

    public function save($options = NULL)
    {
        // Make sure there is something to do
        if (empty(self::$instance->doc->fileCache))
        {
            Kohana::log('debug', 'Asterisk -> Not saving - nothing was changed!');
            
            return TRUE;
        }

        Kohana::log('debug', 'Asterisk -> Saving queued changes to Asterisk config files (via AMI)');

        // get the asterisk manager interface
        $ami = self::$instance->ami;

        // make sure we are connected/can connect
        if (!self::connectAMI($ami))
        {
            Kohana::log('error', 'Asterisk -> Failed to connect to Asterisk AMI interface.');
            
            return FALSE;
        }

        // Loop through all the filenames and contexts that have changed
        foreach(self::$instance->doc->fileCache as $filename => $contexts)
        {
            // Loop through all the config contexts
            foreach($contexts as $context => $lines)
            {
                // Remove this config section (ignoring any errors during) and re-create and empty one
                if ($context != 'general')
                {
                    $ami->queueConfigUpdate($filename, 'DelCat', $context, NULL, NULL, array(
                        'ignoreResponse' => AsteriskManager::AMI_DEL_FAIL2
                    ));
                }

                // Only re-add this context if there's something actually in it
                if ($lines)
                {
                    // Go through and add the category and all associated lines back to the file
                    if ($context != 'general')
                    {
                        $ami->queueConfigUpdate($filename, 'NewCat', $context);
                    }

                    // Loop through all the updates for this config section
                    foreach($lines as $lineNum => $line)
                    {
                        // try to split the update on the =
                        $lineParts = explode('=', $line, 2);

                        // if the split returned false (no = found) or fewer than two parts
                        if (!empty($lineParts) && count($lineParts) == 2)
                        {
                            $ami->queueConfigUpdate($filename, 'Append', $context, $lineParts[0], $lineParts[1], array(
                                'skippredelete' => TRUE
                            ));
                        } 
                        else
                        {
                            Kohana::log('error', 'Asterisk -> Unable to queue \'' . $line . '\' for [' . $context . '] in ' . $filename);
                        }
                    }
                }
            }

            // Clear the internal queue of actions
            try
            {
                $ami->commitConfigUpdates();
            }
            catch(AsteriskManager_Exception $e)
            {
                // This is a place holder for later error checking
                Kohana::log('error', 'Asterisk -> Exception during commit for [' . $context . '] in ' . $filename . ': ' . $e->getMessage());
            }
        }
    }

    public function commit()
    {
        // get the asterisk manager interface
        $ami = self::$instance->ami;

        // make sure we are connected/can connect
        if (!self::connectAMI($ami))
        {
            Kohana::log('error', 'Asterisk -> Failed to connect to Asterisk AMI interface.');

            return FALSE;
        }

        $ami->send('reload');

        return TRUE;
    }

    public function render()
    {
        
    }

    public function reset()
    {
        // Are we already instantiated? If not, create an instance.
        if (!is_object(self::$instance->ami) || get_class(self::$instance->ami) != 'AsteriskManager')
        {
            self::$instance->ami = new AsteriskManager();
        } 
        else
        {
            // We are instantiated - close any existing AMI connections
            Kohana::log('debug', 'Asterisk -> Disconnecting from active Asterisk server');

            try
            {
                //self::$instance->ami->logoff();
            }
            catch(Exception $e)
            {
                // If we can't disconnect, no biggie. Ignore    
            }

            self::$instance->ami->cancelConfigUpdates();
        }

        // Reset the in-memory config document
        if (self::$instance->doc)
        {
            self::$instance->doc->reset();
        } 
        else
        {
            self::$instance->doc = new AsteriskDoc();
        }
    }

    private static function connectAMI($ami)
    {
        // is the $ami a valid AsteriskManager object?
        if (!is_object($ami) || get_class($ami) != 'AsteriskManager')
        {
            return FALSE;
        }

        // Are we already connected? Don't connect again.
        if (!$ami->connected())
        {
            $config = array(
                'host' => Kohana::config('asterisk.AmiHost') ,
                'port' => Kohana::config('asterisk.AmiPort') ,
                'username' => Kohana::config('asterisk.AmiUser') ,
                'password' => Kohana::config('asterisk.AmiPass')
            );

            $ami->setConfig($config);

            try
            {
                $ami->connect();
            }
            catch(AsteriskManager_Exception $e)
            {
                // Server unavailable for whatever reason.
                Kohana::log('error', 'Unable to connect to Asterisk AMI interface: ' . $e->getMessage());

                return FALSE;
            }
        }

        return TRUE;
    }
}