<?php

class DaData
{
    private $_type; //{'serv','admin','vh','special'}
    private $_id;
    private $_root;
    private $_path;
    private $_conferr;

    public function __construct($type, $path, $id = null)
    {
        $this->_type = $type;
        $this->_id = $id;
        $this->_path = $path;

        if ($type == DInfo::CT_EX) {
            $this->init_special();
        } else {
            $this->init();
        }
    }

    public function GetRootNode()
    {
        return $this->_root;
    }

    public function GetId()
    {
        return $this->_id;
    }

    public function GetType()
    {
        return $this->_type;
    }

    public function GetConfErr()
    {
        return $this->_conferr;
    }

    public function GetChildrenValues($location, $ref = '')
    {
        $vals = array();
        $layer = $this->_root->GetChildrenByLoc($location, $ref);
        if ($layer != null) {
            if (is_array($layer))
                $vals = array_map('strval', array_keys($layer));
            else
                $vals[] = $layer->Get(CNode::FLD_VAL);
        }
        return $vals;
    }

    public function GetChildVal($location, $ref = '')
    {
        $layer = $this->_root->GetChildrenByLoc($location, $ref);
        if ($layer != null && is_a($layer, 'CNode'))
            return $layer->Get(CNode::FLD_VAL);
        else
            return null;
    }

    public function GetChildNodeById($key, $id)
    {
        return $this->_root->GetChildNodeById($key, $id);
    }

    public function SetRootNode($nd)
    {
        $this->_root = $nd;
        $this->_root->SetVal($this->_path);
        $this->_root->Set(CNode::FLD_TYPE, CNode::T_ROOT);
    }

    public function SavePost($extractData, $disp)
    {
        $tid = $disp->GetLast(DInfo::FLD_TID);
        if ($this->_type == DInfo::CT_EX)
            $ref = $disp->GetLast(DInfo::FLD_REF);
        else
            $ref = $disp->Get(DInfo::FLD_REF);
        $tblmap = DPageDef::GetPage($disp)->GetTblMap();
        $location = $tblmap->FindTblLoc($tid);
        $this->_root->UpdateChildren($location, $ref, $extractData);

        $this->SaveFile();
    }

    public function DeleteEntry($disp)
    {
        $tid = $disp->GetLast(DInfo::FLD_TID);
        if ($this->_type == DInfo::CT_EX)
            $ref = $disp->GetLast(DInfo::FLD_REF);
        else
            $ref = $disp->Get(DInfo::FLD_REF);
        $tblmap = DPageDef::GetPage($disp)->GetTblMap();
        $location = $tblmap->FindTblLoc($tid);

        $layer = $this->_root->GetChildrenByLoc($location, $ref);
        if ($layer != null) {
            $layer->RemoveFromParent();
            $this->SaveFile();
        } else {
            error_log("cannot find delete entry\n");
        }
    }

    public function SaveFile()
    {
        if ($this->_type == DInfo::CT_EX) {
            return $this->save_special();
        }

        $filemap = DPageDef::GetInstance()->GetFileMap($this->_type);   // serv, vh, tp, admin
        $root = $this->save_conf_file($this->_root, $filemap, $this->_path);
    }

    private function save_conf_file($root, $filemap, $filepath)
    {
        $convertedroot = $root->DupHolder();
        $filemap->Convert(1, $root, 1, $convertedroot);

        $confbuf = '';
        $this->before_write_conf($convertedroot);
        $convertedroot->PrintBuf($confbuf);
        if (!defined('_CONF_READONLY_')) {
            $this->write_file($filepath, $confbuf);
        }
        return $convertedroot;
    }

    private function before_write_conf($root)
    {
        if ($this->_type == DInfo::CT_SERV && ($listeners = $root->GetChildren('listener')) != null) {
            if (!is_array($listeners))
                $listeners = array($listeners);
            foreach ($listeners as $l) {
                if (($maps = $l->GetChildren('vhmap')) != null) {
                    if (!is_array($maps))
                        $maps = array($maps);
                    foreach ($maps as $map) {
                        $vn = $map->Get(CNode::FLD_VAL);
                        $domain = $map->GetChildVal('domain');
                        $l->AddChild(new CNode('map', "$vn $domain"));
                    }
                    $l->RemoveChild('vhmap');
                }
            }
        }

        $loc = 'scripthandler';
        if (($sh = $root->GetChildren($loc)) != null) {
            if (($shc = $sh->GetChildren('addsuffix')) != null) {
                if (!is_array($shc))
                    $shc = array($shc);
                foreach ($shc as $shcv) {
                    $suffix = $shcv->Get(CNode::FLD_VAL);
                    $type = $shcv->GetChildVal('type');
                    $handler = $shcv->GetChildVal('handler');
                    $sh->AddChild(new CNode('add', "$type:$handler $suffix"));
                }
                $sh->RemoveChild('addsuffix');
            }
        }

        if ($this->_type == DInfo::CT_VH) {
            $loc = 'context';
            if (($ctxs = $root->GetChildren($loc)) != null) {
                if (!is_array($ctxs))
                    $ctxs = array($ctxs);
                $order = 1;
                foreach ($ctxs as $ctx) {
                    if ($ctx->GetChildVal('type') === 'null') {
                        $ctx->RemoveChild('type'); // default is static (null), do not write to file
                    }
                }
            }
        }

    }

    private function after_read($disp)
    {
        $type = $disp->Get(DInfo::FLD_ConfType);
        $view = $disp->Get(DInfo::FLD_View);
        $viewname = $disp->Get(DInfo::FLD_ViewName);
        $pid = $disp->Get(DInfo::FLD_PID);

        if ($type == DInfo::CT_SERV) {
            $serverName = $this->_root->GetChildVal('serverName');
            if ($serverName == '$HOSTNAME' || $serverName == '') {
                $serverName = php_uname('n');
            }
            $this->_id = $serverName;

            if ($view == 'serv' && $pid == 'g') {
                $runningAs = 'user(' . $this->_root->GetChildVal('user') .
                        ') : group(' . $this->_root->GetChildVal('group') . ')';
                $this->_root->AddChild(new CNode('runningAs', $runningAs));
            }
        }

        if (in_array($view, ['sl', 'sl_','al','al_'])) {
            if (($listeners = $this->_root->GetChildren('listener')) != null) {
                if (!is_array($listeners))
                    $listeners = array($listeners);
                foreach ($listeners as $l) {
                    $addr = $l->GetChildVal('address');
                    if ($pos = strrpos($addr, ':')) {
                        $ip = substr($addr, 0, $pos);
                        if ($ip == '*')
                            $ip = 'ANY';
                        $l->AddChild(new CNode('ip', $ip));
                        $l->AddChild(new CNode('port', substr($addr, $pos + 1)));
                    }
                    if (($maps = $l->GetChildren('map')) != null) {
                        if (!is_array($maps))
                            $maps = array($maps);
                        foreach ($maps as $map) {
                            $mapval = $map->Get(CNode::FLD_VAL);
                            if (($pos = strpos($mapval, ' ')) > 0) {
                                $vn = substr($mapval, 0, $pos);
                                $domain = trim(substr($mapval, $pos + 1));
                                $anode = new CNode('vhmap', $vn);
                                $anode->AddChild(new CNode('vhost', $vn));
                                $anode->AddChild(new CNode('domain', $domain));
                                $l->AddChild($anode);
                            }
                        }
                        $l->RemoveChild('map');
                    }
                }
            }
        }

        if ($view == 'vh_') {
            $vhnode = $this->_root->GetChildNodeById('virtualhost', $viewname);
            if ($vhnode) {
                $loc = 'context';
                if (($ctxs = $vhnode->GetChildren($loc)) != null) {
                    if (!is_array($ctxs))
                        $ctxs = array($ctxs);
                    $order = 1;
                    foreach ($ctxs as $ctx) {
                        if ($ctx->GetChildren('type') == null) {
                            $ctx->AddChild(new CNode('type', 'null')); // default is static (null)
                        }
                        $ctx->AddChild(new CNode('order', $order++));
                    }
                }
            }
        }

        if ($pid == 'sh') {
            if ($view == 'vh_') {
                $node = $this->_root->GetChildNodeById('virtualhost', $viewname);
            } else {
                $node = $this->_root;
            }
            $loc = 'scripthandler';
            if ($node != null && (($sh = $node->GetChildren($loc)) != null)) {
                if (($shc = $sh->GetChildren('add')) != null) {
                    if (!is_array($shc))
                        $shc = array($shc);
                    foreach ($shc as $shcv) {
                        $typeval = $shcv->Get(CNode::FLD_VAL);
                        if (preg_match("/^(\w+):(\S+)\s+(.+)$/", $typeval, $m)) {
                            $anode = new CNode('addsuffix', $m[3]);
                            $anode->AddChild(new CNode('suffix', $m[3]));
                            $anode->AddChild(new CNode('type', $m[1]));
                            $anode->AddChild(new CNode('handler', $m[2]));
                            $sh->AddChild($anode);
                        }
                    }
                    $sh->RemoveChild('add');
                }
            }
        }
    }

    private function getBypassInclude($disp)
    {
        if ($this->_type != DInfo::CT_SERV) {
            return null;
        }
        $bypass = [];
        $v = $disp->Get(DInfo::FLD_View) ;
        $pid = $disp->Get(DInfo::FLD_PID);

        if ($v != 'vh' && $v != 'vh_') {
            $bypass[] = '/vhosts\.conf/';
            $bypass[] = '/ips\.conf/';
        }
        if ($v != 'sl' && $v != 'sl_') {
            $bypass[] = '/listeners\.conf/';
        }
        if ($pid != 'mod') {
            $bypass[] = '/modsecurity\-enable\.conf/';
            $bypass[] = '/lscache\.conf/';
        }
        if ($pid != 'appserver') {
            $bypass[] = '/railsdefaults\.conf/';
            $bypass[] = '/wsgidefaults\.conf/';
            $bypass[] = '/nodedefaults\.conf/';
        }
        if ($pid != 'tuning') {
            $bypass[] = '/tuning\.conf/';
        }
        return $bypass;
    }

    private function init()
    {
        if (!file_exists($this->_path) || filesize($this->_path) < 10) {
            $this->_conferr = 'Failed to find config file at ' . $this->_path;
            return false;
        }
        $disp = DInfo::singleton();
        //error_log($disp->ShowDebugInfo());

        $parser = new PlainConfParser();

        $bypass = $this->getBypassInclude($disp);
        if (!empty($bypass)) {
            $parser->SetBypassInclude($bypass);
        }
        $this->_root = $parser->Parse($this->_path);
        if ($this->_root->HasFatalErr()) {
            $this->_conferr = $this->_root->GetErr();
            error_log("fatel err " . $this->_root->GetErr());
            return false;
        }
        if ($parser->HasInclude()) {
            // readonly
            if (!defined('_CONF_READONLY_')) {
                define('_CONF_READONLY_', true);
            }
        }

        $this->after_read($disp);
        return true;
    }

    private function init_special()
    {
        $lines = file($this->_path);
        if ($lines === false) {
            return false;
        }

        $this->_root = new CNode(CNode::K_ROOT, $this->_id, CNode::T_ROOT);
        $items = array();

        if ($this->_id == 'MIME') {
            foreach ($lines as $line) {
                if (($c = strpos($line, '=')) > 0) {
                    $suffix = trim(substr($line, 0, $c));
                    $type = trim(substr($line, $c + 1));
                    $m = new CNode('index', $suffix);
                    $m->AddChild(new CNode('suffix', $suffix));
                    $m->AddChild(new CNode('type', $type));
                    $items[$suffix] = $m;
                }
            }
        } elseif ($this->_id == 'ADMUSR' || $this->_id == 'V_UDB') {
            foreach ($lines as $line) {
                $parsed = explode(':', trim($line));
                $size = count($parsed);
                if ($size == 2 || $size == 3) {
                    $name = trim($parsed[0]);
                    $pass = trim($parsed[1]);
                    if ($name != '' && $pass != '') {
                        $u = new CNode('index', $name);
                        $u->AddChild(new CNode('name', $name));
                        $u->AddChild(new CNode('passwd', $pass));
                        if ($size == 3 && (($group = trim($parsed[2])) != '')) {
                            $u->AddChild(new CNode('group', $group));
                        }
                        $items[$name] = $u;
                    }
                }
            }
        } elseif ($this->_id == 'V_GDB') {
            foreach ($lines as $line) {
                $parsed = explode(':', trim($line));
                if (count($parsed) == 2) {
                    $group = trim($parsed[0]);
                    $users = trim($parsed[1]);
                    if ($group != '') {
                        $g = new CNode('index', $group);
                        $g->AddChild(new CNode('name', $group));
                        $g->AddChild(new CNode('users', $users));
                        $items[$group] = $g;
                    }
                }
            }
        }

        ksort($items, SORT_STRING);
        reset($items);
        foreach ($items as $item) {
            $this->_root->AddChild($item);
        }
        return true;
    }

    private function save_special()
    {
        $fd = fopen($this->_path, 'w');
        if (!$fd) {
            return false;
        }

        $items = $this->_root->GetChildren('index');

        if ($items != null) {
            if (is_array($items)) {
                ksort($items, SORT_STRING);
                reset($items);
            } else
                $items = array($items);

            foreach ($items as $key => $item) {
                $line = '';
                if ($this->_id == 'MIME') {
                    $line = str_pad($key, 8) . ' = ' . $item->GetChildVal('type') . "\n";
                } elseif ($this->_id == 'ADMUSR' || $this->_id == 'V_UDB') {
                    $line = $item->GetChildVal('name') . ':' . $item->GetChildVal('passwd');
                    $group = $item->GetChildVal('group');
                    if ($group != null)
                        $line .= ':' . $group;
                    $line .= "\n";
                }
                else if ($this->_id == 'V_GDB') {
                    $line = $key . ':' . $item->GetChildVal('users') . "\n";
                }
                fputs($fd, $line);
            }
        }
        fclose($fd);
        return true;
    }

    private function write_file($filepath, $buf)
    {
        if (!file_exists($filepath)) {
            // new file, check path exists
            if (!PathTool::createFile("{$filepath}.new", $err)) {
                error_log("failed to create file $filepath : $err \n");
                return false;
            }
        }

        $fd = fopen("{$filepath}.new", 'w');
        if (!$fd) {
            error_log("failed to open in write mode for {$filepath}.new");
            return false;
        }

        if (fwrite($fd, $buf) === false) {
            error_log("failed to write temp config for {$filepath}.new");
            return false;
        }
        fclose($fd);

        @unlink("{$filepath}.bak");
        if (file_exists($filepath) && !rename($filepath, "{$filepath}.bak")) {
            error_log("failed to rename {$filepath} to {$filepath}.bak");
            return false;
        }

        if (!rename("{$filepath}.new", $filepath)) {
            error_log("failed to rename {$filepath}.new to {$filepath}");
            return false;
        }

        return true;
    }

}
