<?php

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

class TiptelPhoneProvisioning extends VirtualPhoneProvisioningController
{

    const LOG_TAG = 'tiptel-dls';

    private $tiptelNULL = '%NULL%';

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

    function startsWith($haystack, $needle)
    {
        return $needle === "" || strpos($haystack, $needle) === 0;
    }

    protected function checkUserAgent($ua)
    {
        //Tiptel IP Serie...
        $ua = str_replace("  ", " ", $ua);

        // Yealink-T22P 7.71.0.125 00:15:65:4d:49:81
        if (startsWith($ua, "Yealink-")) {
            $ua_parts = explode("-", $ua);
            $ua_parts[1] = "SIP-" . $ua_parts[1];
            $ua = implode(' ', $ua_parts);
        }

        Log::d("requested Provisioning $ua", self::LOG_TAG);

        $ua_parts = explode(' ', $ua);
        if (strToLower(@$ua_parts[0]) === 'tiptel') {
            $found = false;
            if (strToLower(@$ua_parts[1]) === 'ip') {
                $ua_model = strToLower(@$ua_parts[2]);

                foreach ($this->_getSupportedPhoneModels() as $model) {
                    if ($model->getUserAgent() == $ua_model) {
                        $this->setCurrentPhoneModel($model);
                        $found = true;
                        break;
                    }
                }
            }

            # find out the firmware version of the phone
            $fw_vers = (preg_match('/(\d+\.\d+\.\d+\.\d+)/', @$ua_parts[3], $m)) ? $m[1] : '0.0.0.0';
            $fw_vers_nrml = $this->tiptel_normalize_version($fw_vers);
            $this->setCurrentFirmwareVersion($fw_vers_nrml);
            return $found;
        }

        // Yealink VP-2009
        //VideoPhone 23.40.0.20 00:15:65:22:5c:19
        if (strToLower(@$ua_parts[0]) === 'videophone') {

            $this->Log(GS_LOG_DEBUG, "User Agent is $ua");
            $found = false;
            foreach ($this->_getSupportedPhoneModels() as $model) {
                if ($model->getUserAgent() == "VP-2009") {
                    $this->setCurrentPhoneModel($model);
                    $found = true;
                    break;
                }
            }

            # find out the firmware version of the phone
            $fw_vers = (preg_match('/(\d+\.\d+\.\d+\.\d+)/', @$ua_parts[2], $m)) ? $m[1] : '0.0.0.0';
            $fw_vers_nrml = $this->tiptel_normalize_version($fw_vers);
            $this->setCurrentFirmwareVersion($fw_vers_nrml);

            return $found;
        }

        //Add Yealink processing here...
        //yealink SIP-T22P
        if (strToLower(@$ua_parts[0]) === 'yealink') {

            $found = false;
            if (substr(strToLower(@$ua_parts[1]), 0, 3) === 'sip') {
                $ua_model = strToLower(@$ua_parts[1]);

                foreach ($this->_getSupportedPhoneModels() as $model) {
                    if ($model->getUserAgent() == $ua_model) {
                        $this->setCurrentPhoneModel($model);
                        $found = true;
                        break;
                    }
                }
            }

            # find out the firmware version of the phone
            $fw_vers = (preg_match('/(\d+\.\d+\.\d+\.\d+)/', @$ua_parts[2], $m)) ? $m[1] : '0.0.0.0';
            $fw_vers_nrml = $this->tiptel_normalize_version($fw_vers);
            $this->setCurrentFirmwareVersion($fw_vers_nrml);
            return $found;
        }
        return parent::checkUserAgent($ua);
    }

    protected function runFirmwareUpdate(Firmware $fwUpdate)
    {
        $this->psetting('firmware|path', '/tmp/download.cfg');
        $this->psetting('firmware|server_type', 'http');
        $this->psetting('firmware|server_ip', cfg('PROV_HOST'));
        $this->psetting('firmware|server_port', cfg('PROV_PORT'));
        $this->psetting('firmware|login_name', '');
        $this->psetting('firmware|login_pswd', '');
        if (cfg('PROV_PORT') != "") {
            $this->psetting('firmware|http_url', "http://" . cfg('PROV_HOST') . ":" . cfg('PROV_PORT') . "/firmware/");
        } else {
            $this->psetting('firmware|http_url', "http://" . cfg('PROV_HOST') . "/firmware/");
        }
        $this->psetting('firmware|firmware_name', $fwUpdate->fileName);
        return false;
    }

    protected function loadConfigValues($mac = NULL)
    {
        $template = $this->getTemplate();
        $Phone = $this->getPhoneModel();

        $SipAccount = $Phone->getSipAccount();

        $User = $SipAccount->getUser();
        $callwaiting = false;
        $buttonsound = false;
        $closePowerLight = false;
        if ($User && $User->call_waiting) {
            $callwaiting = true;
        }

        if ($User && $User->key_tone) {
            $buttonsound = true;
        }

        foreach ($template as $configfile => $values) {
            //generate psetting
            foreach ($values as $key => $value) {
                $this->psetting($configfile . '|' . $key, $value);
            }
        }

        $this->psetting('account|SIPServerHost', $SipAccount->getSIPRegistrar());
        $this->psetting('account|SIPServerPort', cfg("INTERNAL_SIP_PORT"));
        $this->psetting('account|UserName', $SipAccount->authname);
        $this->psetting('account|AuthName', $SipAccount->authname);
        $this->psetting('account|password', $SipAccount->secret);
        $this->psetting('account|DisplayName', $SipAccount->getDisplayName());

        $label = $SipAccount->getShortLabel(true);
        // check if user has callforwarding -> Change displayname!
        if ($User) {
            if ($User->defaultCallForwardSet != $User->callForwardSet) {
                $preset = ForwardPreset::model()->findByPk($User->callForwardSet);
                $closePowerLight = true;
                if ($preset != NULL) {
                    $label = $preset->name;
                } else {
                    $label = "FWD?";
                }
            }
        }

        $this->psetting('account|Label', $label);

        $this->psetting('autoprovision|server_address', $this->getProvURL());

        $this->psetting('account|SubscribeRegister', '1');
        // $this->psetting('account|SubscribeMWI', '1');
        // MAYBE causes hold/retrieve bug -> has to been disable until this has been fixed in Firmware
        if (cfg("RTP_ENABLE_SRTP") == "1") {
            $this->psetting('account|srtp_encryption', '2');
        } else {
            $this->psetting('account|srtp_encryption', '0');
        }

        $this->psetting('account|Expire', $SipAccount->expires);
        $this->psetting('account|SubscribeMWIExpire', $SipAccount->expires);
        $this->psetting('account|SessionExpires', $SipAccount->expires);
        $this->psetting('blf|SubscribePeriod', $SipAccount->expires);

        $this->psetting('Message|VoiceNumber0', 'voicemail');

        $tz = cfg("TIMEZONE");
        if ($User && $User->user_timezone != "") {
            $tz = $User->user_timezone;
        }

        $tz_offset = get_timezone_offset("UTC", $tz, true);
        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('Time|TimeZone', $tz_offset); # -11 to +23
        $this->psetting('Time|TimeServer1', cfg("NTP_HOST")); # NTP Server 1

        $time_format = cfg("USER_TIME_FORMAT");
        if ($User && $User->user_time_format != "") {
            $time_format = $User->user_time_format;
        }

        if ($time_format == "24") {
            $this->psetting('Time|TimeFormat', "1");
        } else {
            $this->psetting('Time|TimeFormat', "0");
        }

        $this->psetting('Features|Call_Waiting', ($callwaiting ? '1' : '0'));
        $this->psetting('Features|ButtonSoundOn', ($buttonsound ? '1' : '0'));
        $this->psetting('Features|ClosePowerLight', ($closePowerLight ? '1' : '0'));
        $this->psetting('Features|ActionURILimitIP', cfg('PROV_HOST'));

        // Phone-GUI Language
        $language = cfg("GUI_LANGS");
        if ($User && $User->user_language != "") {
            $language = $User->user_language;
        }
        $this->psetting("Lang|ActiveWebLanguage", $this->convertLanguageCode($language));
        $this->psetting("Lang|WebLanguage", $this->convertLanguageCode($language));

        // %NULL% = Yealink/Tiptel need this to set the key to default
        $this->psetting('programablekey2|Label', $this->tiptelNULL);
        $this->psetting('programablekey3|Label', $this->tiptelNULL);
        $this->psetting('programablekey14|path', "/config/vpPhone/vpPhone.ini");
        $this->psetting('programablekey14|DKtype', "2");

        if (!$User) {
            //Login-App
            $this->psetting('programablekey3|DKtype', "27");
            $this->psetting('programablekey3|Value', $this->getProvURL() . "login/" . $Phone->mac_addr . ".xml");
            $this->psetting('programablekey3|Label', "Login");
        }

        if ($User) {
            // XML-Phonebook
            $this->psetting('programablekey2|DKtype', "27");
            $this->psetting('programablekey2|Value', $this->getProvURL() . "pb/" . $Phone->mac_addr . ".xml");
            $this->psetting('programablekey2|Label', $this->getXmlPhonebookKeyName($language));

            // XML-Voicemail-Screen
            $this->psetting('programablekey3|DKtype', "27");
            $this->psetting('programablekey3|Value', $this->getProvURL() . "voicemail/" . $Phone->mac_addr . ".xml");
            $this->psetting('programablekey3|Label', $this->getXmlVoicemailKeyName($language));

            // XML-ConfigApp => Transfer key
            $this->psetting('programablekey14|path', "/config/vpPhone/vpPhone.ini");
            $this->psetting('programablekey14|DKtype', "27");
            $this->psetting('programablekey14|Line', "0");
            $this->psetting('programablekey14|XMLPhoneBook', "");
            $this->psetting('programablekey14|HistoryType', "0");
            $this->psetting('programablekey14|Value', $this->getProvURL() . "cfgapp/" . $Phone->mac_addr . ".xml?section=forwarding");
        }

        //Set default Memory11-Key
        $this->psetting('memory11|path', "/config/vpPhone/vpPhone.ini");
        $this->psetting('memory11|Line', "0");
        $this->psetting('memory11|DKtype', "15");
        $this->psetting('memory11|Value', '');

        $this->psetting('DTMF|DTMFInbandTransfer', '2');
        $this->psetting('DTMF|InfoType', '2');

        $this->setVolumeSettings($User);
        $this->setAudioCodecs();

        $this->processSoftkeys();

        $this->changeSettingsWithProvisioningProfile($User, $Phone->type);
        return true;
    }

    protected function processSoftkeys()
    {
        $Phone = $this->getPhoneModel();
        $SipAccount = $Phone->getSipAccount();
        $User = $SipAccount->getUser();

        $levels = array("line", "memory", 'ext0', 'ext1', 'ext2');

        foreach ($levels as $level) {

            $softKeys = $Phone->getCurrentPhoneKeys($User, $level);

            foreach ($softKeys as $softKey) {
                $this->processSoftKey($softKey);
            }
        }
    }

    protected function processSoftKey(PhoneKey $key)
    {
        if ($key->function === 'no') {
            return;
        }
        $level = $key->level;
        $parameter = $key->name;
        if (startsWith($level, 'ext')) {
            $parameter = $key->key_idx;
        }

        $key->function = $this->setKeyFunctionForOldKeyProfiles($key->function);
        $keyFunction = $this->convertSoftKeyFunction($key->function, 16);
        $keyValue = $this->convertSoftKeyValue($key);
        $keyLabel = $key->label;
        $pickupValue = $this->convertPickupCode($key->function, array(16, 'X06'), $this->tiptelNULL);

        if (empty($keyFunction)) {
            if ($key->level == 'line') {
                $keyFunction = 15;
            }
            $keyValue = $this->tiptelNULL;   // %NULL% = Yealink/Tiptel need this to set the key to default
            $keyLabel = $this->tiptelNULL;
        }
        return $this->setSoftKeySettings($parameter, $keyFunction, $keyValue, $keyLabel, $pickupValue, $level);
    }

    protected function setKeyFunctionForOldKeyProfiles($keyFunction)
    {
        if ($keyFunction == 5) {
            $keyFunction = 'X08';
        } else if ($keyFunction == 12) {
            $keyFunction = 'X05';
        } else if ($keyFunction == 14) {
            $keyFunction = 'X07';
        }
        return $keyFunction;
    }

    protected function setSoftKeySettings($parameter, $keyFunction, $keyValue, $keyLabel, $pickupValue, $level)
    {
        $path = '/config/vpPhone/vpPhone.ini';
        if (startsWith($level, 'ext')) {
            $levelNumber = (int) str_replace('ext', '', $level) + 1;
            $path = "/config/vpPhone/Ext38_0000000000000$levelNumber.cfg";
        }
        $this->psetting($parameter . '|path', $path);
        $this->psetting($parameter . '|DKtype', $keyFunction);
        $this->psetting($parameter . '|Line', "0");
        $this->psetting($parameter . '|Value', $keyValue);
        $this->psetting($parameter . '|Label', $keyLabel);
        $this->psetting($parameter . '|PickupValue', $pickupValue);
    }

    protected function generateOutput()
    {
        foreach ($this->mCurrentSettings as $section_name => $section_values) {
            //Yii::log('[ '.$section_name.' ]', "error", "provisioning" );
            Log::d('[ ' . $section_name . ' ]', self::LOG_TAG);

            if (substr($section_name, 0, 7) === 'account') {
                $section_name = 'account';
            }

            if (substr($section_name, 0, 5) === 'audio' && strpos($section_name, '_') !== false) {
                $section_name = substr($section_name, 0, strpos($section_name, "_"));
            }

            if (substr($section_name, 0, 3) === 'Key') {
                $section_name = 'Key' . substr($section_name, 5, strpos($section_name, "."));
            }

            echo '[ ', $section_name, ' ]', "\n";

            foreach ($section_values as $name => $val) {
                //Yii::log( $name .' = '. $val, "error", "provisioning" );
                Log::d($name . ' = ' . $val, self::LOG_TAG);
                echo $name . ' = ' . $val, "\n";
            }
            echo "\n";
        }
        return false;
    }

    protected function _getSupportedPhoneModels()
    {
        return Tiptel::getSupportedPhoneModels();
    }

    private function tiptel_normalize_version($fwvers)
    {
        $tmp = explode('.', $fwvers);
        $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], 3, '0', STR_PAD_LEFT);
        return $v0 . '.' . $v1 . '.' . $v2 . '.' . $v3;
    }

    private function getTemplate()
    {
        $contents = file_get_contents(dirname(__FILE__) . "/templates/" . $this->mCurrentPhoneModel->getPhoneModelName() . ".tpl");

        $contents = explode("\n", $contents);
        $config = array();
        $currentsection = "";
        foreach ($contents as $line) {

            $line = trim($line);

            if (strlen($line) == 0) {
                continue;
            }

            if ($line[0] == "#") {
                continue;
            }

            if ($line[0] == ";") {
                continue;
            }


            if ($line[0] == "[") {
                $currentsection = $line;
                $currentsection = trim($currentsection, "][ ");
                continue;
            }

            $parts = explode("=", $line);

            $key = trim($parts[0]);
            $value = trim(@$parts[1]);

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

    protected function psetting($sectionname, $val = '')
    {
        if (strPos($sectionname, '|')) {
            list($section, $name) = explode('|', $sectionname);
            if ($section !== '' && $name !== '') {
                if (!array_key_exists($section, $this->mCurrentSettings) || !is_array($this->mCurrentSettings[$section])) {
                    $this->mCurrentSettings[$section] = array();
                }
                $this->mCurrentSettings[$section][$name] = $val;
            }
        }
    }

    protected function getXmlPhonebookKeyName($language)
    {
        $keyName = "Dir.";
        if ($language === "de_de") {
            $keyName = "Verz.";
        }
        return $keyName;
    }

    protected function getXmlVoicemailKeyName($language)
    {
        $keyName = "VMail";
        if ($language === "de_de") {
            $keyName = "AB";
        }
        return $keyName;
    }

    protected function getAccountCount()
    {
        $count = 0;
        foreach ($this->mCurrentSettings as $key => $value) {
            if (strpos($key, 'account') === 0) {
                $count++;
            }
        }
        return $count;
    }

    protected function setAudioCodecs()
    {
        $phonalisaCodecs = str_replace(' ', '', cfg('GLOBAL_CODEC_PREFS'));
        $codecs = explode(',', $phonalisaCodecs);
        $phoneCodecs = $this->getPhoneCodecs();
        $accountCount = $this->getAccountCount();

        foreach ($codecs as $key => $value) {
            if (array_key_exists($value, $phoneCodecs)) {
                $phoneCodecs[$value]['enable'] = 1;
                $oldPriority = $phoneCodecs[$value]['priority'];
                $oldCodecWithPriority = $this->searchForPriority($key, $phoneCodecs);
                $phoneCodecs[$oldCodecWithPriority]['priority'] = $oldPriority;
                $phoneCodecs[$value]['priority'] = $key;
            }
        }

        for ($i = 0; $i <= (int) $accountCount; $i++) {
            $this->setAudioCodecToSettings($phoneCodecs, $i);
        }
    }

    protected function getPhoneCodecs()
    {
        $codecs = array(
            'PCMU' => 0,
            'PCMA' => 8,
            'G723_53' => 4,
            'G723_63' => 4,
            'G729' => 18,
            'G722' => 9,
            'iLBC' => 106,
            'G726-16' => 112,
            'G726-24' => 102,
            'G726-32' => 2,
            'G726-40' => 104,
            'GSM' => 3
        );
        $result = array();
        $priority = 0;
        foreach ($codecs as $codec => $rtpmap) {
            $result[$codec] = array(
                'enable' => 0,
                'PayloadType' => $codec,
                'priority' => $priority,
                'rtpmap' => $rtpmap,
            );
            $priority++;
        }
        return $result;
    }

    protected function searchForPriority($id, $array)
    {
        foreach ($array as $key => $val) {
            if ($val['priority'] === $id) {
                return $key;
            }
        }
        return null;
    }

    protected function setAudioCodecToSettings($phoneCodecs, $account)
    {
        $priority = 0;
        foreach ($phoneCodecs as $codecKey => $codec) {
            $key = "audio$priority" . "_$account";
            if ($account == 0) {
                $key = "audio$priority";
            }
            $this->psetting("$key|path", "/config/voip/sipAccount$account.cfg");
            $this->psetting("$key|enable", $codec['enable']);
            $this->psetting("$key|PayloadType", $codecKey);
            $this->psetting("$key|priority", $codec['priority']);
            $this->psetting("$key|rtpmap", $codec['rtpmap']);
            $priority++;
        }
    }

    protected function setVolumeSettings($User)
    {
        if ($User) {
            $this->setVolumeUserSettings($User);
        } else {
            $this->psetting('PhoneSetting|RingVol', 8);
            $this->psetting('PhoneSetting|HandSetSpkVol', 8);
            $this->psetting('PhoneSetting|HandSetMicVol', 8);
            $this->psetting('PhoneSetting|HeadSetSpkVol', 8);
            $this->psetting('PhoneSetting|HeadSetMicVol', 8);
            $this->psetting('PhoneSetting|HandFreeSpkVol', 8);
            $this->psetting('PhoneSetting|HandFreeMicVol', 8);
        }
    }

    protected function setVolumeUserSettings($User)
    {
        $userSettings = $User->getSettings();
        if ($userSettings) {
            $settings = $userSettings->attributes;
            unset($settings['userId']);
            foreach ($settings as $key => $value) {
                if (empty($value)) {
                    return $this->setVolumeSettings(false);
                }
                $divisor = 15 / 10;
                $value = (int) $value * $divisor;
                $settings[$key] = round($value);
            }

            $this->psetting('PhoneSetting|RingVol', $settings['ringVolume']);
            $this->psetting('PhoneSetting|HandSetSpkVol', $settings['receiverVolume']);
            $this->psetting('SignalToneVol|Handset', $settings['receiverVolume']);
            $this->psetting('PhoneSetting|HandSetMicVol', $settings['receiverMicroVolume']);
            $this->psetting('PhoneSetting|HeadSetSpkVol', $settings['headsetVolume']);
            $this->psetting('SignalToneVol|Headset', $settings['headsetVolume']);
            $this->psetting('PhoneSetting|HeadSetMicVol', $settings['headsetMicroVolume']);
            $this->psetting('PhoneSetting|HandFreeSpkVol', $settings['speakerVolume']);
            $this->psetting('SignalToneVol|Handfree', $settings['speakerVolume']);
            $this->psetting('PhoneSetting|HandFreeMicVol', $settings['speakerMicroVolume']);
        }
    }
}
