<?php

require_once dirname(__FILE__) . '/../../lib/phoneprovisioning.php';
require_once dirname(__FILE__) . '/../../lib/AbstractPhoneModel.php';
require_once "TimezoneHelper.php";
require_once "Grandstream.php";

class GrandstreamPhoneModel extends AbstractPhoneModel
{

    const LOG_TAG = 'grandstream-dls';

    private $mLineKeys = 0;
    private $mKeys = 0;

    public function setLineKeys($keys)
    {
        $this->mLineKeys = $keys;
    }

    public function setKeys($keys)
    {
        $this->mKeys = $keys;
    }

    public function getLineKeys()
    {
        return $this->mLineKeys;
    }

    public function getKeys()
    {
        return $this->mKeys;
    }
}

class GrandstreamPhoneProvisioning extends VirtualPhoneProvisioningController
{

    private $phoneModelName = null;

    public function __construct()
    {
        $this->mManufactorName = "Grandstream";
    }

    protected function getProvURL($mac = null)
    {
        $protocol = cfg('PROV_PROTOCOL');
        $host = cfg('PROV_HOST');
        $port = cfg('PROV_PORT');
        $basepath = cfg('PROV_BASEPATH');
        $phoneType = $this->phoneModelName;

        if ($port != '') {
            $port = ":" . $port;
        }

        if (in_array($phoneType, array('gxp2000', 'gxp2020', 'gxp2010'), true)) {
            $length = strlen($basepath) - 1;
            $find = substr($basepath, $length);
            if ($find == "/") {
                $basepath = substr($basepath, 0, $length);
            } else {
                $basepath = '';
            }
        }
        return $protocol . $host . $port . $basepath;
    }

    protected function getFirmwarePath($mac)
    {
        $phoneType = $this->phoneModelName;
        $firmwarePath = $this->getProvURL($mac);
        if (in_array($phoneType, array('gxp280', 'gxp1200', 'gxp2000', 'gxp2010', 'gxp2020', 'gxv3000', 'gxv3005'), true)) {
            if (substr($firmwarePath, -1) !== '/') {
                $firmwarePath .= "/";
            }
            $firmwarePath .= "grandstream/";
        }
        return $firmwarePath;
    }

    protected function checkMacAddress($mac)
    {
        return parent::checkMacAddress($mac);
    }

    protected function checkCurrentFirmwareVersion($version)
    {
        if (preg_match('/(\d+\.\d+\.\d+\.\d+)/', @$version, $m)) {
            $ua_fwvers = $m[0];
        } else {
            $ua_fwvers = '0.0.0.0';
        }
        return $ua_fwvers;
    }

    protected function grandstream_normalize_version($version)
    {
        $currentFirmware = $this->checkCurrentFirmwareVersion($version);
        $tmp = explode('.', $currentFirmware);

        $v0 = str_pad((int) @$tmp[0], 2, '0', STR_PAD_LEFT);
        $v1 = str_pad((int) @$tmp[1], 2, '0', STR_PAD_LEFT);
        $v2 = str_pad((int) @$tmp[2], 2, '0', STR_PAD_LEFT);
        $v3 = str_pad((int) @$tmp[3], 2, '0', STR_PAD_LEFT);
        return $v0 . '.' . $v1 . '.' . $v2 . '.' . $v3;
    }

    protected function checkUserAgent($ua)
    {
        $ua = str_replace("  ", " ", $ua);
        $ua_parts = explode(' ', $ua);

        if (strToLower(@$ua_parts[0]) === 'grandstream') {
            $found = false;

            if (
                    preg_match('/(ht|bt|gxp|gxv)[0-9]{1,6}/', strToLower(@$ua_parts[2]), $pregParts) ||
                    preg_match('/(ht|bt|gxp|gxv)[0-9]{1,6}/', strToLower(@$ua_parts[3]), $pregParts)
            ) {
                $ua_model = $pregParts[0];
                $this->phoneModelName = $pregParts[0];

                foreach (Grandstream::getSupportedPhoneModels() as $model) {
                    if ($model->getUserAgent() == $ua_model) {
                        $this->setCurrentPhoneModel($model);
                        $found = true;
                        break;
                    }
                }
                $ua_fwvers = $this->getFirmwareVersionFromUserAgent($ua_parts);
                $this->setCurrentFirmwareVersion($ua_fwvers);
            }
            return $found;
        }
        Log::w("User Agent is invalid $ua", self::LOG_TAG);
        return false;
    }

    protected function getFirmwareVersionFromUserAgent($ua_parts)
    {
        $firmwarePart = @$ua_parts[2];
        if (strtolower($this->phoneModelName) === 'gxp1450') {
            $firmwarePart = @$ua_parts[5];
        }
        return $this->grandstream_normalize_version($firmwarePart);
    }

    protected function psetting($sectionname, $val = '')
    {
        if (preg_match('/^P[0-9]{1,4}$/S', $sectionname)) {
            $this->mCurrentSettings[$sectionname] = $val;
        }
    }

    protected function loadConfigValues($mac = NULL)
    {
        Log::d('loadConfigValues', self::LOG_TAG);
        $template = $this->getTemplate();
        $phoneModelName = $this->phoneModelName;
        $phone = $this->getPhoneModel();
        $SipAccount = $phone->getSipAccount();
        $user = $SipAccount->getUser();

        foreach ($template as $key => $value) {
            $this->psetting($key, $value);
        }

        $this->setNetworkConfigValues($mac);
        $this->setDateAndTimeConfigValues($phoneModelName, $user);
        $this->setRingtonesConfigValues($phoneModelName);
        $this->setFirmwareUpgradeAndProvisioningConfigValues($mac);
        $this->setXMLSettingsConfigValues($phoneModelName, $mac);
        $this->setSIPSettingsConfigValues($phoneModelName, $SipAccount, $user);
        $this->setKeyConfigValues($phoneModelName, $phone, $user);
        $this->setUserSpecificConfigValues($phoneModelName, $user);
        $this->setAudioCodecs();

        $this->psetting('P194', '0');           // Automatic Upgrade
        $this->psetting('P32', round($SipAccount->expires / 60)); //Register Expiration

        $this->psetting('P33', 'voicemail');    // VoiceMail Dial String
        $language_format = cfg("GUI_LANGS");
        if ($user && $user->user_language != "") {
            $language_format = $user->user_language;
        }

        $this->setLanguageFormat($language_format, $phoneModelName);
        $this->psetting('P1347', cfg('DP_PICKUP_PREFIX')); //BLF Call-pickup Prefix

        $this->changeSettingsWithProvisioningProfile($user, $phone->type);
        return true;
    }

    protected function getShortLabel($sipAccountLabel)
    {
        $shortLabel = explode(' <', $sipAccountLabel);
        if ($shortLabel[0]) {
            $sipAccountLabel = $shortLabel[0];
        }
        return $sipAccountLabel;
    }

    protected function setNetworkConfigValues($mac)
    {
        $this->psetting('P8', '0');    // IP Address Type ( 0 = DHCP, 1 = static)
        $this->psetting('P148', $this->mCurrentPhoneModel->getPhoneModelName());   // DHCP vendor class ID
        $this->psetting('P146', 'grandstream-' . $mac);  // DHCP hostname
    }

    protected function setDateAndTimeConfigValues($phoneType, $user)
    {
        if (in_array($phoneType, array('ht287', 'bt110', 'bt200', 'bt201', 'gxp280', 'gxp1200', 'gxp2000', 'gxp2010', 'gxp2020', 'gxv3000', 'gxv3005'), true)) {
            $tz = cfg("TIMEZONE");
            if ($user && $user->user_timezone != "") {
                $tz = $user->user_timezone;
            }

            $tz_offset = get_timezone_offset("UTC", $tz, true);
            $gmt = '720';
            if ($tz_offset) {
                $gmt = ($tz_offset * 60) + $gmt;
            }
            if ($tz_offset > 0) {
                $tz_offset = "+" . $tz_offset;
            } else if ($tz_offset < 0) {
                $tz_offset = "" . $tz_offset;
            }
            //  Log::d("Timezone offset: ".$tz_offset ." (".cfg("TIMEZONE").")", self::LOG_TAG);
            $this->psetting('P64', $gmt);  // -11 to +23
            $this->psetting('P75', date('I'));  //Daylight Saving Time ( 0 = no, 1 = yes ) - we already have that in the timezone offset P64
        }

        if (in_array($phoneType, array('gxp280', 'gxp1200', 'gxp2000', 'gxp2010', 'gxp2020', 'gxp1450'), true)) {
            if ($user && $user->user_time_format != "") {
                $time_format = $user->user_time_format;
            }
            $time_format = cfg("USER_TIME_FORMAT");

            if ($time_format == "24") {
                $this->psetting('P122', "1");  //Time Display Format. 1 - 24 Hour
            } else {
                $this->psetting('P122', "0");  //Time Display Format. 0 - 12 Hour
            }

            $date_format = cfg('USER_DATE_FORMAT');
            if ($user && $user->user_date_format != "") {
                $date_format = $user->user_date_format;
            }
            if ($date_format == 'dd.MM.yyyy') {
                $date = '2';
            } else if ($date_format == 'MM/dd/yyyy') {
                $date = '1';
            } else {
                $date = '0';
            }
            $this->psetting('P102', $date);
        }
        $this->psetting('P30', cfg('NTP_HOST'));   // NTP Server

        if (in_array($phoneType, array('bt200', 'bt201', 'gxp280', 'gxp1200', 'gxp2000', 'gxp2010', 'gxp2020', 'gxv3000', 'gxv3005', 'gxp1450'), true)) {
            $this->psetting('P143', '1');   // Allow DHCP Option 2 to override Timezone setting ( 0 = no, 1 = yes )
            $this->psetting('P144', '1');   // Allow DHCP Option 42 to override NTP server
        }
    }

    protected function setRingtonesConfigValues($phoneType)
    {
        if (in_array($phoneType, array('gxp280', 'gxp1200', 'gxp2000', 'gxp2010', 'gxp2020', 'gxv3000', 'gxv3005', 'gxv3140', 'gxv3175'), true)) {
            $this->psetting('P105', 'internal');  // Custom ringtone 1, used if incoming caller ID is: ""
            $this->psetting('P106', 'external');  // Custom ringtone 2, used if incoming caller ID is: ""
        }
        $this->psetting('P107', '');  // Custom ringtone 3, used if incoming caller ID is: ""	

        if (in_array($phoneType, array('bt200', 'bt201', 'gxp1200', 'gxp2000'), true)) {
            $this->psetting('P345', '3,2,7,2,0;11,1,7,2,0;60'); // System ringtone ( maxlength 64 )
        }
        if (in_array($phoneType, array('gxp2010', 'gxp2020', 'gxv3000', 'gxv3005', 'gxp1450'), true)) {
            $this->psetting('P345', 'f1=440,f2=480,c=200/400;');    // System ringtone ( maxlength 64 | default "f1=440,f2=480,c=200/400;" )
        }

        if (in_array($phoneType, array('bt200', 'bt201', 'gxp1200', 'gxp2000', 'gxp2010', 'gxp2020', 'gxv3000', 'gxv3005', 'gxp1450'), true)) {
            $this->psetting('P343', 'f1=425,f2=425;');  // Dial Tone ( default f1=350,f2=440; )
            $this->psetting('P344', 'f1=425,f2=425,c=10/10;');  // Message Waiting ( default f1=350,f2=440,c=10/10; )
            $this->psetting('P346', 'f1=425,f2=425,c=100/400;');    // Ring Back Tone ( default f1=440,f2=480,c=200/400; )
            $this->psetting('P347', 'f1=425,f2=425,c=20/20-20/500;');   // Call-Waiting Tone ( default f1=440,f2=440,c=25/525; )
            $this->psetting('P348', 'f1=425,f2=425,c=48/48;');  // Busy Tone ( default f1=480,f2=620,c=50/50; )
            $this->psetting('P349', 'f1=425,f2=425,c=24/24;');  // Reorder Tone ( default f1=480,f2=620,c=25/25; )
            // Syntax: f1=val,f2=val[,c=on1/off1[-on2/off2[-on3/off3]]];
            // (Frequencies are in Hz and cadence on and off are in 10ms)
            // ON is the period of ringing ("On time" in 'ms') while OFF is the period of silence.
            // In order to set a continuous ring, OFF shoud be zero. Otherwise it will ring ON
            // ms and a pause of OFF ms and then repeat the pattern. Up to three cadences are supported.
        }
    }

    protected function setFirmwareUpgradeAndProvisioningConfigValues($mac)
    {
        $this->psetting('P192', $this->getFirmwarePath($mac));   // TFTP/HTTP Firmware Update Server ( based on P212 )
        $this->psetting('P237', $this->getProvURL($mac));   // TFTP/HTTP Config Server ( based on P212 )
        $this->psetting('P240', '0');   // Authenticate Conf File ( 0 = no, 1 = yes )
    }

    protected function setXMLSettingsConfigValues($phoneType, $mac)
    {
        if (in_array($phoneType, array('gxp280', 'gxp1200', 'gxp2000', 'gxp2010', 'gxp2020', 'gxv3000', 'gxv3005', 'gxp1450'), true)) {
            $this->psetting('P330', '0');    // Enable Phonebook XML ( 0 = disable, 1 = http, 2 = tftp )
            $this->psetting('P331', $this->getProvURL($mac));   // Phonebook XML server path ( maxlength 128 ) //FIXME add phonebookXML
            $this->psetting('P332', '120'); // Phonebook Download Interval ( in minutes, between 0-720 )
            $this->psetting('P333', '1');   // Remove manually-edited entries on download ( 0 = no, 1 = yes )
            /*
             * grandstream automatically adds "/gs_phonebook.xml" to the URL
             * e.g. prov/grandstream/gs_phonebook.xml
             */
        }

        if (in_array($phoneType, array('gxp2000', 'gxp2010', 'gxp2020'), true)) {
            $this->psetting('P340', '0');   // Enable Idle Screen XML ( 0 = disable, 1 = http, 2 = tftp )  //FIXME?
            $this->psetting('P341', $this->getProvURL($mac));   // Idle Screen XML server path ( maxlength 128 )  //FIXME?
            /*
             * grandstream automatically adds "/gs_screen.xml" to the URL
             * e.g. prov/grandstream/gs_screen.xml
             * todo: script to change idle_screen for nobody or user
             */
        }
    }

    protected function setSIPSettingsConfigValues($phoneType, $SipAccount, $user)
    {
        $label = $this->convertUmlauts($SipAccount->getLabel());

        if ($phoneType != "gxw-4008") {
            $this->psetting('P35', $SipAccount->authname);  // SIP User ID
            $this->psetting('P36', $SipAccount->authname);  // Authentication ID
            $this->psetting('P34', $SipAccount->secret);    // SIP Authentication Password (cleartext)

            if ($phoneType == "gxp2000") {
                $label = $this->getShortLabel($label);
            }
            // check if user has callforwarding -> Change displayname!
            if ($user) {
                if ($user->defaultCallForwardSet != $user->callForwardSet) {
                    $preset = ForwardPreset::model()->findByPk($user->callForwardSet);
                    if ($preset != NULL) {
                        $label = $preset->name;
                    } else {
                        $label = "FWD?";
                    }
                }
            }
            $this->psetting('P3', $label); // Display (CallerID) Name
        }

        if (in_array($phoneType, array('gxp280', 'gxp1200', 'gxp2000', 'gxp2010', 'gxp2020', 'gxv3000', 'gxv3005', 'gxv3140', 'gxv3175', 'gxp1450'), true)) {
            $this->psetting('P270', $label);   // Account Name ( maxlength 96 )
        }
        $this->psetting('P47', cfg('SIP_HOST'));    // SIP Server	
        $this->psetting('P40', cfg('INTERNAL_SIP_PORT'));   // Local SIP Port ( default 5060 )
    }

    protected function convertUmlauts($label)
    {
        $umlauts = array('Ä', 'ä', 'Ü', 'ü', 'Ö', 'ö', 'ß');
        $convertTo = array('Ae', 'ae', 'Ue', 'ue', 'Oe', 'oe', 'ss');
        $label = str_replace($umlauts, $convertTo, $label);
        return $label;
    }

    protected function setKeyConfigValues($phoneType, $phone, $user)
    {
        if (in_array($phoneType, array('gxp2000', 'gxp2010', 'gxp2020'), true)) {
            $phoneKeys = $phone->getCurrentPhoneKeys($user, 'P');
            /** reset all keys on phone
             * 
             * key layout:
             * Key 1: P323 P301 P302 P303
             * Key 2: P324 P304 P305 P306
             * Key 3: P325 P307 P308 P309
             * ...
             * Key 7: P329 P319 P320 P321
             */
            $max_keys = 7;
            for ($i = 0; $i < $max_keys; ++$i) {
                $this->processSoftKey($phoneKeys[$i]);
                $this->psetting('P' . ($i + 323), ($phoneKeys[$i]) ? $phoneKeys[$i]->function : '0');
                $this->psetting('P' . ($i * 3 + 301), '0');
                $this->psetting('P' . ($i * 3 + 302), ($phoneKeys[$i]) ? $phoneKeys[$i]->label : '');
                $this->psetting('P' . ($i * 3 + 303), ($phoneKeys[$i]) ? $phoneKeys[$i]->number_data : '');
            }
            if (in_array($phoneType, array('gxp2010'), true)) {
                /*
                 * key layout (only GXP2010)
                 * Key  8: P353 P354 P355 P356
                 * Key  9: P357 P358 P359 P360
                 * Key 10: P361 P362 P363 P364
                 * ...
                 * Key 18: P393 P394 P395 P396
                 */
                $max_keys = 11;
                for ($i = 0; $i < $max_keys; ++$i) {
                    $this->processSoftKey($phoneKeys[$i]);
                    $this->psetting('P' . ($i * 4 + 353), ($phoneKeys[$i]) ? $phoneKeys[$i]->function : '0' );
                    $this->psetting('P' . ($i * 4 + 354), '0');
                    $this->psetting('P' . ($i * 4 + 355), ($phoneKeys[$i]) ? $phoneKeys[$i]->label : '');
                    $this->psetting('P' . ($i * 4 + 356), ($phoneKeys[$i]) ? $phoneKeys[$i]->number_data : '' );
                }
            }
            /*
             * reset all keys on ext 1 
             * key layout:
             * Key  1: P6001 P6201 P6401 P6601
             * Key  2: P6002 P6202 P6402 P6602
             * Key  3: P6003 P6203 P6403 P6603
             * ...
             * Key 56: P6056 P6256 P6456 P6656
             */
            $extKeys1 = $phone->getCurrentPhoneKeys($user, 'EXT1');
            $max_keys = 56;
            for ($i = 0; $i < $max_keys; ++$i) {
                $this->processSoftKey($extKeys1[$i]);
                $this->psetting('P' . ($i + 6001), ($extKeys1[$i]) ? $extKeys1[$i]->function : '0' );
                $this->psetting('P' . ($i + 6201), '0');
                $this->psetting('P' . ($i + 6401), ($extKeys1[$i]) ? $extKeys1[$i]->label : '' );
                $this->psetting('P' . ($i + 6601), ($extKeys1[$i]) ? $extKeys1[$i]->number_data : '' );
            }
            /*
             * reset all keys on ext 2
             * 
             * key layout:
             * Key  57: P6057 P6257 P6457 P6657
             * Key  58: P6058 P6258 P6458 P6658
             * Key  59: P6059 P6259 P6459 P6659
             * ...
             * Key 112: P6112 P6312 P6512 P6712  
             */
            $extKeys2 = $phone->getCurrentPhoneKeys($user, 'EXT2');
            $max_keys = 56;
            for ($i = 0; $i < $max_keys; ++$i) {
                $this->processSoftKey($extKeys2[$i]);
                $this->psetting('P' . ($i + 6057), ($extKeys2[$i]) ? $extKeys2[$i]->function : '0' );
                $this->psetting('P' . ($i + 6257), '0');
                $this->psetting('P' . ($i + 6457), ($extKeys2[$i]) ? $extKeys2[$i]->label : '' );
                $this->psetting('P' . ($i + 6657), ($extKeys2[$i]) ? $extKeys2[$i]->number_data : '' );
            }
        }
    }

    protected function processSoftKey($phoneKey)
    {

        $number = $phoneKey->number_data;
        $function = $phoneKey->function;
        if ($function == 'X01') {
            $phoneKey->function = '1';
            $phoneKey->number_data = cfg('DP_FWD_ON_PREFIX') . $number;
        }
        if ($function == 'X02') {
            $phoneKey->function = '1';
            $phoneKey->number_data = cfg('DP_SPEED_DIAL_ON_PREFIX') . $number;
        }
        if ($function == 'X03') {
            $phoneKey->function = '1';
            $phoneKey->number_data = '*5' . $number;
        }
    }

    protected function setUserSpecificConfigValues($phoneType, $user)
    {
        if (in_array($phoneType, array('ht287', 'bt110', 'bt200', 'bt201', 'gxp280', 'gxp1200', 'gxp2000', 'gxp2010', 'gxp2020', 'gxv3000', 'gxv3005', 'gxp1450'), true)) {
            if ($user && $user->call_waiting != '') {
                $this->psetting('P91', '1');   // Disable Call Waiting ( 0 = no, 1 = yes )
            } else {
                $this->psetting('P91', '0');   // Disable Call Waiting ( 0 = no, 1 = yes )
            }
        }

        if (in_array($phoneType, array('gxp1200', 'gxp2000', 'gxp2010', 'gxp2020', 'gxp1450'), true)) {
            if ($user && $user->call_waiting != '') {
                $this->psetting('P186', '1');  // Disable Call-Waiting Tone ( 0 = no, 1 = yes )
            } else {
                $this->psetting('P186', '0');  // Disable Call-Waiting Tone ( 0 = no, 1 = yes )
            }
            $this->psetting('P1340', '0');  // Disable DND ( 0 = no, 1 = yes )
        }
    }

    private function setLanguageFormat($language_format, $phoneType)
    {
        if (in_array($phoneType, array('gxp280', 'gxp1200', 'gxp2000', 'gxp2010', 'gxp2020', 'gxv3000', 'gxv3005'), true)) {
            $languageFilePrefix = $this->mCurrentSettings['P399'];  // language file prefix ( e.g. german => gxp_german.lpf, maxlength 32 )
            $displayLanguage = $this->mCurrentSettings['P342'];     // display language ( 0 = english, 2 = chinese, 3 = P399 )
            $firmwarePath = $this->mCurrentSettings['P192'];    //TFTP/HTTP Firmware Update Server ( based on P212 )
            $phoneTypePrefix = subStr($phoneType, 0, 3);

            if ($displayLanguage == '0' || $displayLanguage == '2') {
                $languageFilePrefix = '';
                $displayLanguage = $displayLanguage;
            } else {
                $displayLanguage = '3';
                if ($language_format == 'de_de') {
                    if ($languageFilePrefix == '') {
                        $languageFilePrefix = 'german';
                    }
                } else {
                    if ($languageFilePrefix == '') {
                        $languageFilePrefix = '';
                    }
                }

                $langfile = $firmwarePath . $phoneTypePrefix . '_' . strtolower($languageFilePrefix) . '.lpf';
                $checkIfFileExistst = $this->checkIfLanguageFileExists($langfile);

                if ($checkIfFileExistst != true) {
                    $displayLanguage = '0';
                    $languageFilePrefix = '';
                }
            }

            $this->psetting('P399', $languageFilePrefix);
            $this->psetting('P342', $displayLanguage);
        } else {
            $this->psetting('P1362', substr($language_format, 0, 2)); // Display Language
        }
    }

    private function checkIfLanguageFileExists($langfile)
    {
        try {
            $file = @file_get_contents($langfile);
            if ($file === false) {
                throw new Exception('No language-file found!');
            } else {
                return true;
            }
        } catch (Exception $e) {
            return false;
        }
    }

    private function getTemplate()
    {
        $phone = $this->phoneModelName;

        if (in_array($phone, array('gxp2000', 'gxp2020', 'gxp1200', 'gxp2010', 'gxp280', 'gxp285'), true)) {
            $templateName = 'gxp';
        } else {
            $templateName = $phone;
        }
        $contents = file_get_contents(dirname(__FILE__) . "/templates/grandstream-" . $templateName . ".tpl");

        $contents = explode("\n", $contents);
        $config = array();
        foreach ($contents as $line) {
            $line = trim($line);
            if (strlen($line) == 0) {
                continue;
            }

            if ($line[0] == "#") {
                continue;
            }
            $parts = explode("=", $line);
            $key = trim($parts[0]);
            $value = trim(@$parts[1]);

            $config[$key] = $value;
        }
        return $config;
    }

    protected function settings_out()
    {
        $data = '';
        foreach ($this->mCurrentSettings as $name => $val) {
            $val = preg_replace_callback("/([^A-Za-z0-9.*_\-])/", function($matches) {
                return ($matches[1] == ' ') ? '+' : sprintf('%%%02x', ord($matches[1]));
            }, $val);
            $data .= $name . '=' . $val . '&';
        }
        $data .= 'gnkey=0b82';
        return $data;
    }

    protected function generateOutput()
    {
        $content = $this->settings_out();
        $mac = $_REQUEST['mac'];

        $header = array();
        // 00 01 02 04 - put out_length / 2 in later
        $header[] = 0x00;
        $header[] = 0x00;
        $header[] = 0x00;
        $header[] = 0x00;

        // 04 05 - put checksum in later
        $header[] = 0x00;
        $header[] = 0x00;

        // 06 07 08 09 0a 0b - MAC address
        $header[] = intval(subStr($mac, 0, 2), 16);
        $header[] = intval(subStr($mac, 2, 2), 16);
        $header[] = intval(subStr($mac, 4, 2), 16);
        $header[] = intval(subStr($mac, 6, 2), 16);
        $header[] = intval(subStr($mac, 8, 2), 16);
        $header[] = intval(subStr($mac, 10, 2), 16);

        // 0c 0d 0e 0f - CR LF CR LF
        $header[] = 0x0d; // CR
        $header[] = 0x0a; // LF
        $header[] = 0x0d; // CR
        $header[] = 0x0a; // LF

        echo $this->grandstream_binary_output($header, $content);

        if (!headers_sent()) {
            header('Content-Type: application/octet-stream');
        }
        return false; // ?
    }

    protected function grandstream_binary_output_checksum($str)
    {
        $sum = 0;
        for ($i = 0; $i <= ((strlen($str) - 1) / 2); $i++) {
            $sum += ord(subStr($str, 2 * $i, 1)) << 8;
            $sum += ord(subStr($str, (2 * $i) + 1, 1));
            $sum &= 0xffff;
        }
        $sum = 0x10000 - $sum;
        return array(($sum >> 8) & 0xff, $sum & 0xff);
    }

    protected function grandstream_binary_output($header, $body)
    {
        // $body length must be divisible by 2
        if ((strlen($body) % 2) == 1) {
            $body .= chr(0);
        }

        // get length
        $body_length = strlen($body);
        $header_length = count($header);
        $out_length = $header_length + $body_length;

        // 00 01 02 03 - out_length / 2
        $header[0] = (($out_length / 2) >> 24) & 0xff;
        $header[1] = (($out_length / 2) >> 16) & 0xff;
        $header[2] = (($out_length / 2) >> 8) & 0xff;
        $header[3] = (($out_length / 2) ) & 0xff;

        // assemble output
        $arr = $header;
        array_unshift($arr, 'C' . $header_length);
        $initstr = call_user_func_array('pack', $arr);
        $checktext = $initstr . $body;

        array_splice($header, 4, 2, $this->grandstream_binary_output_checksum($checktext));

        $arr = $header;
        array_unshift($arr, 'C' . $header_length);
        $initstr = call_user_func_array('pack', $arr);
        $out = $initstr . $body;
        return $out;
    }

    protected function setAudioCodecs()
    {
        $phonalisaCodecs = str_replace(' ', '', cfg('GLOBAL_CODEC_PREFS'));
        $codecs = explode(',', $phonalisaCodecs);

        $phoneKey = 51;
        $account1PhoneKey = 57;

        foreach ($codecs as $codec) {
            if ($this->getPhoneReadableCodec($codec) === false) {
                continue;
            }

            $this->psetting("P$account1PhoneKey", $this->getPhoneReadableCodec($codec));   //Account 1: P57-P62; P46; P98 
            $this->psetting("P4$phoneKey", $this->getPhoneReadableCodec($codec));          //Account 2: P451-P458
            $this->psetting("P5$phoneKey", $this->getPhoneReadableCodec($codec));          //Account 3: P551-P558
            $this->psetting("P6$phoneKey", $this->getPhoneReadableCodec($codec));          //Account 4: P651-P658

            if ($account1PhoneKey == 98) {
                break;
            }

            if ($account1PhoneKey == 46) {
                $account1PhoneKey = 98;
            }

            if ($account1PhoneKey < 62) {
                (int) $account1PhoneKey++;
            } else if ($account1PhoneKey != 98) {
                $account1PhoneKey = 46;
            }

            if ($phoneKey < 58) {
                (int) $phoneKey++;
            }
        }
    }

    protected function getPhoneReadableCodec($codecs)
    {
        $phoneCodecs = array(
            'GSM' => '3',
            'G723' => '4',
            'G729' => '18',
            'PCMU' => '0',
            'PCMA' => '8',
            'G726-32' => '2',
            'G722' => '9',
            'iLBC' => '98'
        );

        if (!isset($phoneCodecs[$codecs])) {
            return false;
        }

        return $phoneCodecs[$codecs];
    }
}
