<?php

class config
{
    // public properties
    var $path;
    var $strict = true;

    // public functions

    function parse(&$error)
    {
        /* check the path variable has been set */
        if (! $this->path) {
            trigger_error('CLASS config->write: No path to configuration file set.', E_USER_ERROR);
        }
        
        /* open the configuration file */
        $fhwnd = fopen($this->path, 'r');
        
        if (! $fhwnd) {
            trigger_error('CLASS Config: Error Opening Configuration File', E_USER_ERROR);
        }


        $continue = true;
        $linenumber = 0;

        while (! feof($fhwnd) && $continue) {
            $line = trim(fgets($fhwnd));
            ++$linenumber;

            if ($line == '') continue;
            
            
            $found = false;
            
            if (! $this->_is_comment($line)) { /* check the line is not a comment */
                if ($setting = $this->_get_setting($line)) { /* chck if line contains a setting */
                    /* find the setting  */
                    foreach ($this->_settings as $name => $setting_info) {
                        if ($name == $setting['name']) {
                            $this->_settings[$name]['exists'] = true;
                            
                            $found = true;
                            $valid = false;

                            if ($setting['value'] == '' && $this->_settings[$name]['required']) {
                                $error[] = Array(SETTING_NO_VALUE, $name, '', $linenumber, '');

                                if ($this->strict) $continue = false;
                            } else if (! $this->_set_value($name, $setting['value'], $error_text)) {
                                $error[] = Array(SETTING_CALLBACK_FALIURE, $name, $setting['value'], $linenumber, $error_text);
                                
                                if ($this->strict) $continue = false;
                            }
                        }
                    }

                    if (! $found) {
                        $error[] = Array(SETTING_FILE_NOT_FOUND, $setting['name'], $setting['value'], $linenumber);

                        if ($this->strict) $continue = false;
                    }
                } else { /*line is invalid format */
                    $error[] = Array(SETTING_INVALID_FORMAT, $line, '', $linenumber);

                    if ($this->strict) $continue = false;
                }
            }
        }

        fclose($fhwnd);

        if (! $continue) {
            return false;
        }

                
        /* 
         * for any settings which have not been found set their defualt value
         * or add an error if the required flag is true
         */
        foreach ($this->_settings as $name => $setting) {
            if (! $setting['exists']) {
                if (! $setting['required']) {                
                    $this->_settings[$name]['value'] = $setting['default'];
                    $this->_settings[$name]['set'] = true;
                } else {
                    $error[] = Array(SETTING_NOT_FOUND, $name);
                }
            }

            $this->_settings[$name]['exists'] = false;
        }

        return true;                            
    }

    function write()
    {
        /* check the path variable has been set */
        if (! $this->path) {
            trigger_error('CLASS config->write: No path to configurationfile set.', E_USER_ERROR);
        }
        
        /* check all settings have values - setting default values where necessary */
        foreach ($this->_settings as $name => $setting) {
            if (! $setting['set']) {
                if ($setting['required']) {
                    trigger_error("CLASS config->write: required setting '$name' has no value.", E_USER_ERROR);
                } else {
                    $this->_settings[$name]['value'] = $setting['default'];
                    $this->_settings[$name]['set'] = true;
                }
            }
        }
        
        /* open and read the config file */
        $fhwnd = fopen($this->path, 'rb');

        if (! $fhwnd) {
            trigger_error('CLASS config->write: Error opening configuration file.', E_USER_ERROR);
        }
        
        while (! feof($fhwnd)) {
            $file_array[] = fgets($fhwnd);
        }
        
        /* check the type of line ending */
        if (substr($file_array[0], -2) == "\r\n") {
            $eol= "\r\n";
        } else {
            $eol = "\n";
        }
        
        /* loop through each line checking if it is a stored setting */
        for ($count = 0; $count < count($file_array); $count++) {
            if ($file_setting = $this->_get_setting($file_array[$count])) {
                foreach ($this->_settings as $name => $setting) {
                    if ($file_setting['name'] == $name) {
                        $file_array[$count] = "$name = {$setting['value']}$eol";
                        $this->_settings[$name]['exists'] = true;
                    }
                }
            }
        }

        /* add any settings not found to the end of the file array */
        $file_array[] = $eol;
        
        foreach ($this->_settings as $name => $setting) {
            if (! $setting['exists']) {
                $file_array[] = "$name = {$setting['value']}$eol";
            }

            $this->_setting[$name]['exists'] = false;
        }

        fclose($fhwnd);

        /* re-open and truncate the config file */
        $fhwnd = fopen($this->path, 'wb');

        if (! $fhwnd) {
            trigger_error('CLASS config->write: Error opening configuration file.', E_USER_ERROR);
        }

        /* write the file array to the config file */
        for ($count = 0; $count < count($file_array); $count++) {
            fwrite($fhwnd, $file_array[$count], strlen($file_array[$count]));
        }

        fclose($fhwnd);
    }

    function add_setting($name, $required = true, $default = '', $callback = null)
    {
        /* check $callback has been set */
        if (!is_null($callback)) {
            /* check the function is a function and we can call it */
            if (! is_callable($callback)) {
                if ($callback == 'bool') {
                    $callback = Array(&$this, '_check_bool');
                } else if ($callback == 'num') {
                    $callback = Array(&$this, '_check_num');
                } else if ($callback == 'int') {
                    $callback = Array(&$this, '_check_int');
                } else {
                    trigger_error('CLASS config->add_setting: Callback function not callable.', E_USER_ERROR);
                }
            }
        }

        /* check the setting does not exist */
        if (array_key_exists($name, $this->_settings)) {
            trigger_error('CALSS config->add_setting: setting exists.', E_USER_ERROR);
        }

        $this->_settings[$name] = Array('value' => '',
                                        'required' => $required,   
                                        'set' => false,
                                        'exists' => false,
                                        'default' => $default,
                                        'callback' => $callback);        
    }

    function remove_setting($name)
    {
        if (! array_key_exists($name, $this->_settings)) {
            trigger_error('CLASS config->remove_setting: Setting does not exist.', E_USER_ERROR);
        }

        unset($this->_settings[$name]);
    }

    function get($name)
    {
        /* check the setting exists */
        if (! array_key_exists($name, $this->_settings)) {
            trigger_error('CLASS config->get: Setting does not exist.', E_USER_ERROR);
        } else {
            if (! $_setting[$name]['set']) {
                trigger_error('CLASS config->get: Setting has no value set.', E_USER_ERROR);
            }
        }

        return $_setting[$name]['value'];
    }

    function set($name, $value)
    {
        $error_text = 'Value callback faliure.';
        
        /* check the setting exists */
        if (! array_key_exists($name, $this->_settings)) {
            trigger_error('CLASS config->set: Setting does not exist.', E_USER_ERROR);
        } else {
            if (! $this->_set_value($name, $value, $error_text)) {
                trigger_error("CLASS: config->set: $error_text.", E_USER_ERROR);
            }
        }
    }

    /* this function returns true if the setting has been set by the set function or
     * through the use of the parse function
     *
     * this function should be used before retrieving the value of a setting
     */
    function is_set($name)
    {
        if (! array_key_exists($name, $this->_settings)) {
            trigger_error('CLASS config->is_set: Setting does not exist.', E_USER_ERROR);
        } else {
            return $this->_setting[$name]['set'];
        }
    }   

    // private properties
    var $_settings = Array();

    // private functions
    
    function _is_comment($line)
    {
        $line = trim($line);
        
        if (substr($line, 0, 1) == '#') {
            return true;
        } else {
            return false;
        }
    }
    
    function _get_setting($line)
    {
        $regexp = "/^((?i)[A-Z0-9_]+)\s*=(\s*(.*))?$/";

        
        if (preg_match($regexp, $line, $matches)) {
            return Array('name' => $matches[1], 'value' => $matches[3]);
        } else {
            return false;
        }
    }

    function _set_value ($name, $value, &$error) 
    {
            $setting = &$this->_settings[$name];
            
            /* execute the callback function , if it exists on the value */
            if (! is_null($setting['callback'])) {
                $result = call_user_func_array($setting['callback'], Array($value, &$error_text));

                if(! $result) {
                    return false;
                }
            }
            
            $setting['value'] = $value;
            $setting['set'] = true;

            return true;
    }
    
    function _check_int($value, &$error_text) 
    {
        if (! preg_match("/^[0-9]+$/", $value)) {
            $error_text = 'Value must be an integer.';
            return false;
        } else {
            return true;
        }
    }

    function _check_number($value, &$error_text)
    {
        if (! is_numeric($value)) {
            $error_text = 'Value must be numeric.';
            return false;
        } else {
            return true;
        }
    }
    
    function _check_bool($value, &$error_text) 
    {
        if (! preg_match("/^yes|no|true|false|on|off|1|0$/i", $value)) {
            $error_ext = 'Value must be a true or false value.';
            return false;
        } else {
            return true;
        }
    }

    function config()
    {
        define('SETTING_NO_VALUE', 1);
        define('SETTING_CALLBACK_FALIURE', 2);
        define('SETTING_FILE_NOT_FOUND', 3);
        define('SETTING_INVALID_FORMAT', 4);
        define('SETTING_NOT_FOUND', 5);
    }       
        
}
