<?php

// namespace imldeployment;

require_once 'project.class.php';


/**
 * config-replacement class
 * reads templatefiles and scans its placeholders for replacements
 *
 * @author hahn
 */
class configreplacement {
    
    /**
     * project class
     * @var type 
     */
    protected $_oProject = false;
    protected $_sPhase = false;
    protected $_aForemanReplacements = false;

    
    /**
     * init
     * @param string  $sProject  optional: project id; you can use setProject() too
     * @return boolean
     */
    public function __construct($sProject = false) {
        if ($sProject){
            $this->setProject($sProject);
        }
        return true;
    }
    

    /**
     * get an array with a flat list of all templatefiles of a build
     * @return boolean|array
     */
    public function getTemplatefiles(){
        if (!$this->_sPhase){
            return false;
        }
        $aReturn = array();

        $aBuildfiles=$this->_oProject->getBuildfilesByPlace($this->_sPhase, 'onhold');
        if (!$aBuildfiles){
            $aBuildfiles=$this->_oProject->getBuildfilesByPlace($this->_sPhase, 'ready2install');
        }
        
        if (!$aBuildfiles || !array_key_exists('types', $aBuildfiles) || !array_key_exists('templates', $aBuildfiles['types'])){
            return false;
        }
        foreach ($aBuildfiles['types']['templates'] as $sFile){
            $aReturn[]=$aBuildfiles['dir'].'/'.$sFile;
        }
        return $aReturn;
    }

    /**
     * get an array with all template files (basename) and its replacement fields
     * @return array
     */
    public function getReplacements(){
        if (!$this->_sPhase){
            return false;
        }
        $aFiles=$this->getTemplatefiles($this->_sPhase);
        if (!$aFiles){
            return false;
        }
        
        $aReturn=array();
        foreach ($aFiles as $sFile){
            // $sFile always exists because it was read from filesystem
            $sContent=file_get_contents($sFile);

            preg_match_all('/\@replace\[[\'\"](.*)[\'\"]\]/U', $sContent, $aMatches);
            $aReturn[$sFile]=$aMatches[1];
       }
        return $aReturn;
    }
    
    /**
     * get effective hostgroup id of the current phase
     * @return integer
     */
    public function getForemanHostgroup(){
        $aPrjConfig=$this->_oProject->getConfig();
        $iForemanHostgroupDefault = (int) $aPrjConfig['deploy']['foreman']['hostgroup'];
        $iForemanHostgroup = (int) $aPrjConfig['phases'][$this->_sPhase]['foreman-hostgroup'];
        return ($iForemanHostgroup===OPTION_DEFAULT) ? $iForemanHostgroupDefault : $iForemanHostgroup;
    }
    
    /**
     * get replacement definitions from foreman
     * @global type $aConfig
     * @return boolean
     */
    protected function _getForemanReplacement(){
        global $aConfig;
        if (!$this->_sPhase){
            return false;
        }
        
        // abort if no foreman connection was configured
        if (!array_key_exists('foreman', $aConfig)) {
            return false;
        }

        // return already cached result
        if (array_key_exists($this->_sPhase, $this->_aForemanReplacements)){
            return $this->_aForemanReplacements[$this->_sPhase];
        }
        
        // rebuilt 
        $this->_aForemanReplacements[$this->_sPhase]=false;
        $iEffectiveHostgroup=$this->getForemanHostgroup();

        // abort if no hostgroup was set
        if($iEffectiveHostgroup<=0){
            return false;
        }

        require_once 'foremanapi.class.php';
        $oForeman=new ForemanApi($aConfig['foreman']);
       
        // get a host of this phase
        $aHosts=$oForeman->read(array(
            'request' => array(
                array('hosts' ),
            ),
            'filter' => array(
                'hostgroup_id' => $iEffectiveHostgroup,
            ),
            'response' => array('name', 'id'),
        ));

        $sHost='';
        $aHostsOfPhase=array();
        
        // HACK: phases are part of the hostname .. but not "live" ... and special handling for demo
        $aPrjConfig=$this->_oProject->getConfig();
        $bIsDemo=(array_key_exists('fileprefix', $aPrjConfig)
                && !strpos($aPrjConfig['fileprefix'], 'demo')===false);
        $sPhase=$bIsDemo ? 'demo' : $this->_sPhase;
        foreach($aHosts as $aName){
            $sName=$aName['name'];
            if (($sPhase==='live' &&
                (
                    strpos($sName, 'preview')===false
                    && strpos($sName, 'stage')===false
                    && strpos($sName, 'demo')===false
                )
            )
               || (strpos($sName, $sPhase))
            ){
                $sHost=$sHost ? $sHost : $sName;
                $aHostsOfPhase[]=$sName;
            }
        }
        
        // get created yaml of this host
        $sUrl=$aConfig['foreman']['api']."hosts/$sHost/externalNodes?name=$sHost";
        $aData=$oForeman->makeRequest(array(
            'url'=>$sUrl,
            'method'=>'GET',
        ));
        
        // HACK: minify YAML of the host ... because it is too large for SPYC parser
        $sPart="---\n";
        $bCopy=false;
        foreach(explode("\n", $aData['body']) as $sLine){
            if($bCopy){
                if (strpos($sLine, '    ')===false){
                    $bCopy=false;
                } else {
                    // remove leading spaces and html entities...
                    $sNewLine=  html_entity_decode(preg_replace('/^\ \ \ \ /', '', $sLine, 1));
                    $sNewLine=str_replace('&#39;{', "'{", $sNewLine);
                    $sNewLine=str_replace('}&#39;', "}'", $sNewLine);
                    
                    // fix json errors
                    $sNewLine=str_replace(', }', " }", $sNewLine);
                    $sNewLine=str_replace(',}', "}", $sNewLine);
                    $sPart.=$sNewLine."\n";
                }
            }
            if($sLine==='  iml-deployment-config:'){
                $bCopy=true;
            }
            // echo 'DEBUG: '.($bCopy ? 'COPY':'SKIP').$sLine.'<br>';
        }

        require_once __DIR__.'./../../vendor/spyc/spyc.php';
        $aYaml=spyc_load($sPart);

        $aReturn=array();
        foreach ($aYaml as $aProject){
            foreach ($aProject as $sFile=>$aParams){
                $aReturn[$sFile]=array();
                if (array_key_exists('target', $aParams)){
                    $aReturn[$sFile]['target']=$aParams['target'];
                }
                if (array_key_exists('replace', $aParams)){
                    $aReplace=json_decode($aParams['replace'], 1);
                    $aReturn[$sFile]['replace_source']=$aReplace;
                    foreach ($aReplace as $sVarname=>$value){
                        if (is_array($value) && array_key_exists('_'.$this->_sPhase.'_', $value)){
                            $value=$value['_'.$this->_sPhase.'_'];
                        }
                        $aReturn[$sFile]['replace'][$sVarname]=$value;
                    }
                }
            }
        }

        $this->_aForemanReplacements[$this->_sPhase]=array(
            'rules'=>$aReturn,
            'host'=>$sHost,
            'yaml'=>$sPart,
            'hostgroup'=>$iEffectiveHostgroup,
            'hostsall'=>$aHosts,
            'hostsphase'=>$aHostsOfPhase,
        );
        return $this->_aForemanReplacements[$this->_sPhase];
    }

    /**
     * get foreman base url ... if foreman was activated in the setup
     * 
     * @global array  $aConfig  ci config
     * @return string
     */
    private function _getForemanBaseUrl(){
        global $aConfig;
        if (!array_key_exists('foreman', $aConfig) 
                || !array_key_exists('api', $aConfig['foreman']) 
                || !$aConfig['foreman']['api']
        ){
            return false;
        }
        return $aConfig['foreman']['api'];
    }
    
    /**
     * get html code for links to edit each host of the current phase in foreman
     * 
     * @return boolean
     */
    public function getForemanlink2Host(){
        $sForemanurl=$this->_getForemanBaseUrl();
        if (!$sForemanurl){
            return false;
        }
        $aTmp=$this->_getForemanReplacement();
        if (!array_key_exists('hostsphase', $aTmp)){
            return false;
        }
        require_once 'htmlguielements.class.php';
        $oHtml=new htmlguielements();
        $sReturn='';
        foreach ($aTmp['hostsphase'] as $sHost){
            $sReturn.=$oHtml->getLinkButton(array(
                'href'=>$sForemanurl.'hosts/'.$aTmp['host'],
                'target'=>'_foreman',
                'title'=>t('edit'),
                'icon'=>'host',
                'label'=>t('host').' '.$aTmp['host'],
            )).' ';
        }
        return $sReturn;
    }

    /**
     * get html code for a link to edit hostgroup in foreman
     * 
     * @return boolean
     */
    public function getForemanlink2Hostgroup(){
        $iEffectiveHostgroup=$this->getForemanHostgroup();
        if($iEffectiveHostgroup<=0){
            return false;
        }
        $sForemanurl=$this->_getForemanBaseUrl();
        if (!$sForemanurl){
            return false;
        }
        require_once 'htmlguielements.class.php';
        $oHtml=new htmlguielements();
        return $oHtml->getLinkButton(array(
            'href'=>$sForemanurl.'hostgroups/'.$iEffectiveHostgroup.'/edit',
            'target'=>'_foreman',
            'title'=>t('edit'),
            'icon'=>'hostgroup',
            'label'=>sprintf(t('foreman-hostgroup-id'), $iEffectiveHostgroup),
        ));
        
    }
    
    /**
     * get replacements in foreman 
     * @return type
     */
    public function getForemanReplacements(){
        return $this->_getForemanReplacement();
    }
    
    /**
     * switch to a project
     * @param type $sProject
     */
    public function setProject($sProject, $sPhase=false){
        $this->_oProject = new project($sProject);
        $this->_aForemanReplacements=false;
        $this->setPhase($sPhase);
        return true;
    }
    
    /**
     * set a phase of a project
     * @param string  $sPhase  name of valid phase
     * @return boolean
     */
    public function setPhase($sPhase=false){
        $this->_sPhase=false;
        if (!$sPhase){
            $sPhase=$this->_oProject->getNextPhase(false);
        }
        if (!$sPhase || !$this->_oProject->isActivePhase($sPhase)) {
            return false;
        }
        $this->_sPhase=$sPhase;
        return true;
    }
}
