'server',
        'rds' => 'database',
        's3' => 'hdd',
        'iam' => 'users',
        'guardduty' => 'shield-alt',
        'opensearch' => 'warehouse'
    ];
    protected $pageTemplate = [
        'header.precss' => 'header.precss.template.html',
        'header.postcss' => 'header.postcss.template.html',
        'sidebar.precustom' => 'sidebar.precustom.template.html',
        'sidebar.postcustom' => 'sidebar.postcustom.template.html',
        'breadcrumb' => 'breadcrumb.template.html',
        'footer.prejs' => 'footer.prejs.template.html',
        'footer.postjs' => 'footer.postjs.template.html',
    ];
    protected $isHome = false;
    private $js = [];
    private $jsLib = [];
    private $cssLib = [];
    function __construct($service, $reporter, $services, $regions){
        $this->service = $service;
        $this->services = $services;
        $this->regions = $regions;
        $this->reporter = $reporter;
        $this->idPrefix = $this->service.'-';
    }
    function getHtmlId($el=''){
        $el = $el ?? substr(uniqid(rand(10000,99999)), 0, 11);
        return $this->idPrefix . $el;
    }
    function buildPage(){
        $this->init();
        $output = [];
        $output[] = $this->buildHeader();
        $output[] = $this->buildNav();
        $output[] = $this->buildBreadcrumb();
        $output[] = $this->buildContentSummary();
        $output[] = $this->buildContentDetail();
        $output[] = $this->buildFooter();
        $finalHTML = "";
        foreach($output as $arrayOfText){
            if(!empty($arrayOfText))
                $finalHTML .= implode("\n", $arrayOfText);
        }
        file_put_contents(HTML_DIR.'/'.$this->service.'.html', $finalHTML);
    }
    function init(){
        $this->template = 'default';
    }
    function buildContentSummary(){
        $method = 'buildContentSummary_' . $this->template;
        if(method_exists($this, $method)){
            return $this->$method();
        }else{
            $cls = get_class($this);
            __info("[$cls] Template for ContentSummary not found: " . $method);
        }
    }
    function buildContentDetail(){
        $method = 'buildContentDetail_' . $this->template;
        if(method_exists($this, $method)){
            return $this->$method();
        }else{
            $cls = get_class($this);
            __info("[$cls] Template for ContentDetail not found: " . $method);
        }
    }
    function generateRowWithCol($size=12, $items, $rowHtmlAttr=''){
        $output = [];
        $output[] = "
";
        $__size = $size;
        foreach($items as $ind => $item){
            if(is_array($size)){
                $i = $ind % sizeof($size);
                $__size = $size[$i];
            }
            $output[] = $this->generateCol($__size, $item);
        }
        $output[] = "
";
        return implode("\n", $output);
    }
    function generateCol($size=12, $item){
        $output = [];
        if(empty($item)){
            $output[] = "";
        }else{
            list($html, $divAttr) = $item;
            $output[] = "
";
            $output[] = $html;
            $output[] = "
";
        }
        return implode("\n", $output);
    }
    function generateCard($id, $html, $cardClass='warning', $title='', $titleBadge='',$collapse=false, $noPadding=false){
        $output = [];
        $lteCardClass = empty($cardClass) ? '' : "card-$cardClass";
        $defaultCollapseClass = ($collapse === 9) ? "collapsed-card" : "";
        $defaultCollapseIcon = ($collapse === 9) ? "plus" : "minus";
        $output[] = "
";
        if(!empty($title)){
            $output[] = "";
        }
        $noPadClass = $noPadding ? 'p-0' : '';
        $output[] = "
";
        $output[] = $html;
        $output[] = "
";
        $output[] = "
";
        return implode("\n", $output);
    }
    function generateCategoryBadge($category, $addtionalHtmlAttr){
        $validCategory = ['R', 'S', 'O', 'P', 'C', 'T'];
        $colorByCategory = ['info', 'danger', 'primary', 'success', 'warning', 'info'];
        $nameByCategory = ['Reliability', 'Security', 'Operation Excellence', 'Performance Efficiency', 'Cost Optimization', 'Text'];
        if( !in_array($category, $validCategory)){
            $category = 'X';
            $color = 'info';
            $name = 'Suggestion';
        }else{
            $indexOf = array_search($category, $validCategory);
            $color = $colorByCategory[$indexOf];
            $name = $nameByCategory[$indexOf];
        }
        return "
$name";
    }
    function generatePriorityPrefix($criticality, $addtionalHtmlAttr){
        $validCategory = ['I', 'L', 'M', 'H'];
        $colorByCategory = ['info', 'primary', 'warning', 'danger'];
        $iconByCategory = ['info-circle', 'eye', 'exclamation-triangle', 'ban'];
        $criticality = in_array($criticality, $validCategory) ? $criticality : $validCategory[0];
        $indexOf = array_search($criticality, $validCategory);
        $color = $colorByCategory[$indexOf];
        $icon = $iconByCategory[$indexOf];
        # return "
$criticality";
        return "
";
    }
    function generateSummaryCardContent($summary){
        $output = [];
        $resources = $summary['__affectedResources'];
        $resHtml = [];
        foreach($resources as $region => $resource){
            $items = [];
            $resHtml[] = "
$region: ";
            foreach($resource as $identifier){
                $items[] = "$identifier";
            }
            $resHtml[] = implode(" | ", $items);
            $resHtml[] = "";
        }
        $output[] = "
        - Description
- ".$summary['^description']."
- Resources".implode("", $resHtml);
        $hasTags = $this->generateSummaryCardTag($summary);
        if(strlen(trim($hasTags)) > 0){
            $output[] = "
- Label
- $hasTags";
        }
        if(!empty($summary['__links'])){
            $output[] = "
- Recommendation";
            $output[] = "
- " . implode("
- ", $summary['__links']) . "";
        }
        $output[] = "
";
        return implode("\n", $output);
    }
    function generateDonutPieChart($datasets, $idPrefix = '', $type='doughnut'){
        $id = $idPrefix . $type . uniqid(get_class($this));
        $output = [];
        $output[] = "
            
        
";
        list($labels, $enriched) = $this->__enrichDonutPieData($datasets);
        $this->addJS(
        "var donutPieChartCanvas = $('#$id').get(0).getContext('2d')
        var donutPieData = {
            labels: ".json_encode($labels).",
            datasets: [".json_encode($enriched)."]
        }");
        $this->addJS(
        "var donutPieOptions     = {
		  maintainAspectRatio : false,
		  responsive : true,
		}
		new Chart(donutPieChartCanvas, {
		  type: '$type',
		  data: donutPieData,
		  options: donutPieOptions
		})");
		return implode("\n", $output);
    }
    function generateBarChart($labels, $datasets, $idPrefix = ''){
        $id = $idPrefix . 'bar' . uniqid(get_class($this));
        $output = [];
        $output[] = "
            
        
";
        $enriched = $this->__enrichChartData($datasets);
        $this->addJS("var areaChartData = {
            labels: ".json_encode($labels).",
            datasets: ".json_encode($enriched)."
        }");
        $this->addJS("var barChartData = $.extend(true, {}, areaChartData)
    var stackedBarChartCanvas = $('#$id').get(0).getContext('2d')
    var stackedBarChartData = $.extend(true, {}, barChartData)");
        $this->addJS(
    "var stackedBarChartOptions = {
      responsive              : true,
      maintainAspectRatio     : false,
      scales: {
        xAxes: [{
          stacked: true,
        }],
        yAxes: [{
          stacked: true
        }]
      },
     onClick: function(e, i){
		checkCtrl = $('#checkCtrl')
		var v = i[0]['_model']['label'];
		if(typeof v == 'undefined')
			return
		curVal = checkCtrl.val()
		idx = curVal.indexOf(v)
		if (idx == -1){
			curVal.push(v)
		}else{
			curVal.splice(idx, 1);
		}
		checkCtrl.val(curVal).trigger('change')
    }
}
        new Chart(stackedBarChartCanvas, {
            type: 'bar',
            data: stackedBarChartData,
            options: stackedBarChartOptions
        })");
        return implode("\n", $output);
    }
    function generateSummaryCardTag($summary){
        $text = '';
        $text .= $this->__generateSummaryCardTagHelper($summary['downtime'] ?? false, 'Have Downtime');
        $text .= ' ' . $this->__generateSummaryCardTagHelper($summary['needFullTest'] ?? false, 'Testing Required');
        $text .= ' ' . $this->__generateSummaryCardTagHelper($summary['slowness'] ?? false, 'Performance Impact');
        $text .= ' ' . $this->__generateSummaryCardTagHelper($summary['additionalCost'] ?? false, 'Cost Incurred');
        return $text;
    }
    function __generateSummaryCardTagHelper($flag, $text){
        if($flag == false)
            return;
        $str = $text;
        $color = 'warning';
        if($flag < 0){
            $str .= " (maybe)";
            $color = 'info';
        }
        return "
$str";
    }
    function __enrichDonutPieData($datasets){
        $label = [];
        $arr = [
            'data' => [],
            'backgroundColor' => []
        ];
        
        $idx = 0;
        foreach($datasets as $key => $num){
            $label[] = $key;
            $arr['data'][] = $num;
            $arr['backgroundColor'][] = $this->__randomHexColorCode($idx);
            
            $idx++;
        }
        return [$label, $arr];
    }
    function __enrichChartData($datasets){
        $arr = [];
        $idx = 0;
        foreach($datasets as $key => $num){
            $arr[] = [
                'label' => $key,
                'backgroundColor' => $this->__randomRGB($idx),
                'data' => $num
            ];
            $idx++;
        }
        return $arr;
    }
    function __randomRGB($idx){
        # ["#e27c7c", "#a86464", "#6d4b4b", "#503f3f", "#333333", "#3c4e4b", "#466964", "#599e94", "#6cd4c5"]
        # only random if more than > 9
        $r1Arr = [226, 168, 109, 80 , 51 , 60 , 70 , 89 , 108];
        $r2Arr = [124, 100, 75 , 63 , 51 , 78 , 105, 158, 212];
        $r3Arr = [124, 100, 75 , 63 , 51 , 75 , 100, 148, 197];
        
        if($idx >= sizeof($r1Arr)){
            $r1 = rand(1,255);
            $r2 = rand(1,255);
            $r3 = rand(1,255);    
        }else{
            $r1 = $r1Arr[$idx];
            $r2 = $r2Arr[$idx];
            $r3 = $r3Arr[$idx];
        }
        
        return "rgba($r1, $r2, $r3, 1)";
    }
    function __randomHexColorCode($idx){
        $color = ["#e27c7c", "#a86464", "#6d4b4b", "#503f3f", "#333333", "#3c4e4b", "#466964", "#599e94", "#6cd4c5"];
        if($idx >= sizeof($color))
            return '#' . str_pad(dechex(mt_rand(0, 0xFFFFFF)), 6, '0', STR_PAD_LEFT);
        else
            return $color[$idx];
    }
    function generateTitleWithCategory($count, $title, $category, $color='info'){
        if(empty($category))
            return $title;
        return  "$count. $title 
";
    }
    function generateTable($resource){
        $output = [];
        foreach($resource as $check => $attr){
            $criticality = $attr['criticality'];
            $checkPrefix = '';
            if($criticality == 'H'){
                $checkPrefix = "
 ";
            } else if($criticality == 'M'){
                $checkPrefix = "
 ";
            }
            $output[] = "
";
            $output[] = "| $checkPrefix$check";
            $output[] = " | " . $attr['value'] . "";
            $output[] = " | " . $attr['shortDesc'] . "";
            $output[] = " | 
";
        }
        return implode("\n", $output);
    }
    function __getTemplateByKey($key){
        $path = TEMPLATE_DIR . '/' . $this->pageTemplate[$key];
        if(file_exists($path)){
            return $path;
        }else{
            __warn[$path . ' does not exists'];
            debug_print_backtrace();
        }
    }
    ### ---------- HEADER & FOOTER ----------
    function buildHeader(){
        $output = [];
        #file_get_pre_css
        $headerPreCSS = file_get_contents( $this->__getTemplateByKey('header.precss') );
        $output[] = str_replace(
            ['{$ADVISOR_TITLE}', '{$SERVICE}'],
            [CONFIG::ADVISOR['TITLE'], strtoupper($this->service)],
            $headerPreCSS
        );
        if(!empty($this->cssLib)){
            foreach($this->cssLib as $lib){
                $output[] = "
";
            }
        }
        #file_get_post_css
        $headerPostCSS = file_get_contents( $this->__getTemplateByKey('header.postcss') );
        $output[] = str_replace(
            ['{$ADVISOR_TITLE}'],
            [CONFIG::ADVISOR['TITLE']],
            $headerPostCSS);
        return $output;
    }
    function buildFooter(){
        $output = [];
        #file_get_template preInlineJS
        $preJS = file_get_contents ( $this->__getTemplateByKey('footer.prejs') );
        $preJS = str_replace('"', "'", $preJS);
        $ADMINLTE_VERSION = CONFIG::ADMINLTE['VERSION'];
        $ADMINLTE_DATERANGE = CONFIG::ADMINLTE['DATERANGE'];
        $ADMINLTE_URL = CONFIG::ADMINLTE['URL'];
        $ADMINLTE_TITLE = CONFIG::ADMINLTE['TITLE'];
        $PROJECT_TITLE = CONFIG::ADVISOR['TITLE'];
        $PROJECT_VERSION = CONFIG::ADVISOR['VERSION'];
        eval("\$x = \"$preJS\";");
        $output[] = $x;
        if(!empty($this->jsLib)){
            foreach($jsLib as $lib)
                $output[] = "";
        }
        if(!empty($this->js)){
            $inlineJS = implode("; ", $this->js);
            $output[] = "";
        }
        #file_get_template postInlineJS
        $postJS = file_get_contents ( $this->__getTemplateByKey('footer.postjs') );
        $output[] = $postJS;
        return $output;
    }
    ### [END] ---------- HEADER & FOOTER ----------
    ### ---------- NAVIGATION ----------
    function buildBreadcrumb(){
        $output=[];
        $breadcrumb = file_get_contents( $this->__getTemplateByKey('breadcrumb') );
        $output[] = str_replace(
            ['{$SERVICE}'],
            [strtoupper($this->service)],
            $breadcrumb
        );
        return $output;
    }
    function buildNav(){
        $ISHOME = $this->isHome ? 'active' : '';
        $output = [];
        #file_getsidebar
        $sidebarPRE = file_get_contents( $this->__getTemplateByKey('sidebar.precustom') );
        $output[] = str_replace(
            ['{$ADVISOR_TITLE}', '{$ISHOME}'],
            [CONFIG::ADVISOR['TITLE'], $ISHOME],
            $sidebarPRE
        );
        $arr = $this->buildNavCustomItems();
        $output[] = implode("\n", $arr);
        $sidebarPOST = file_get_contents( $this->__getTemplateByKey('sidebar.postcustom') );
        $output[] = $sidebarPOST;
        return $output;
    }
    function buildNavCustomItems(){
        $services = $this->services;
        $activeService = $this->service;
        $output = [];
        $output[] = "";
        foreach($services as $name => $count){
            $class = $name == $activeService ? 'active' : '';
            $icon = $this->__navIcon($name);
            
            $__count = $count;
            if($name=='guardduty') $__count = '';
            $output[] = "
            
                
                ".strtoupper($name)." {$__count}
            
            
            ";
        }
        return $output;
    }
    function __navIcon($service){
        return self::serviceIcon[$service] ?? 'cog';
    }
    ### [END] ---------- NAVIGATION ----------
    function addJS($js){
        $this->js[] = $js;
    }
    function addJSLib($js){
        $this->jslib[] = $js;
    }
    function addCSSLib($css){
        $this->cssLib[] = $css;
    }
    ########### TEMPLATE HERE #############
    function checkIsLowHangingFruit($attr){
        if($attr['downtime'] == 0 && $attr['additionalCost'] == 0 && $attr['needFullTest'] == 0)
            return true;
        return false;
    }
    function buildContentSummary_default(){
        $output = [];
        ## Chart Building
        $summary = $this->reporter->cardSummary;
        $regions = $this->regions;
        $labels = [];
        $dataSets = [];
        foreach($summary as $label => $attrs){
            $labels[] = $label;
            $res = $attrs['__affectedResources'];
            foreach($regions as $region){
                $cnt = 0;
                if(!empty($res[$region]))
                    $cnt = sizeof($res[$region]);
                $dataSets[$region][] = $cnt;
            }
        }
        $html = $this->generateBarChart($labels, $dataSets);
        $card = $this->generateCard($id=$this->getHtmlId('SummaryChart'), $html, $cardClass='warning', $title='Summary', '', $collapse=true);
        $items = [[$card, '']];
        $output[] = $this->generateRowWithCol($size=12, $items, "data-context='summaryChart'");
        ## Chart completed
        ## Filter
        $filterTitle = "
 Filter";
        $filterByCheck = $this->generateFilterByCheck($labels);
        $filterRow = $this->generateRowWithCol($size=[6, 6, 12], $this->addSummaryControl_default(), "data-context='summary-control'");
        $output[] = $this->generateCard($id='summary-control', $filterByCheck . $filterRow, 'info', $filterTitle, '');
        ## SummaryCard Building
        $items = [];
        foreach($summary as $label => $attrs){
            $body = $this->generateSummaryCardContent($attrs);
            $badge = $this->generatePriorityPrefix($attrs['criticality'], "style='float:right'")
                . ' ' . $this->generateCategoryBadge($attrs['__categoryMain'], "style='float:right'");
            $card = $this->generateCard($id=$this->getHtmlId($label), $body, '', $label, $titleBadge=$badge, $collapse=9);
            $divHtmlAttr = "data-category='".$attrs['__categoryMain']."' data-criticality='".$attrs['criticality']."'";
            if($this->checkIsLowHangingFruit($attrs))
                $divHtmlAttr .= " data-lhf=1";
            $items[] = [$card, $divHtmlAttr];
        }
        $output[] = $this->generateRowWithCol($size=4, $items, "data-context='summary'");
        return $output;
    }
    function buildContentDetail_default(){
        $output = [];
        $output[] = '
Detail
';
        $details = $this->reporter->getDetail();
        $count = 1;
        $previousCategory = "";
        foreach($details as $region => $lists){
            $items = [];
            $output[] = "
$region
";
            foreach($lists as $identifierx => $attrs){
                $tab = [];
                $identifier = $identifierx;
                $category = '';
                $checkIfCategoryPresent = explode('::', $identifierx);
                if(sizeof($checkIfCategoryPresent) == 2){
                    list($category, $identifier) = $checkIfCategoryPresent;
                    if(empty($previousCategory))
                        $previousCategory = $category;
                }
                $tab[] = "
";
                $tab[] = "| Check | Current Value | Recommendation";
                $tab[] = " | 
";
                $tab[] = $this->generateTable($attrs);
                $tab[] = "
";
                $tab = implode("\n", $tab);
                if($previousCategory != $category && $category != '' && $count%2 == 0){
                    $items[] = [];
                }
                $item = $this->generateCard($id=$this->getHtmlId($identifierx), $tab, $cardClass='warning', $title=$this->generateTitleWithCategory($count, $identifier, $category), '', false, true);
                $items[] = [$item, $divHtmlAttr=''];
                $previousCategory = $category;
                $count++;
            }
            $output[] = $this->generateRowWithCol($size=6, $items, "data-context=detail");
        }
        $str = <<
"+t.data('span-category')+"");
})
EOL;
        $this->addJS($str);
        return $output;
    }
    function generateFilterByCheck($labels){
        $output = [];
        $opts = [];
        foreach($labels as $label)
            $opts[] = "";
        $options = implode("", $opts);
        $str = <<