<?php

class StranaspkLogframeController extends BaseController
{

    // StranaspkLogframeController
    public function index() {
        // Function untuk mengambil seluruh logframes yang terkait pada id_periode tertentu
        // berdasarkan masuk id_instansi atau id_logframe (salah satu saja, mutually exclusive, id_instansi diutamakan)
        $this->checkIsAuthenticated();
        $getParams = $this->request->get();
        $params = $this->validate(
            $getParams,
            [
                'id_periode' => ['validators' => ['required', 'digit']],
                'id_instansi' => ['validators' => ['digit']],
                'id_logframe' => ['validators' => ['digit']],
                'level' => ['validators' => ['digit']],
                'limit' => ['validators' => ['digit']],
                'offset' => ['validators' => ['digit']],
            ]
        );
        
        $id_periode = $params['id_periode'];
        $id_instansi = $params['id_instansi'] ?? null;
        $id_logframe = $params['id_logframe'] ?? null;
        $level = $params['level'] ?? null;

        $logframes = [];
        if ($id_instansi) {
            $logframes = $this->findLogframesByInstansi($id_periode, $id_instansi);
        } else if ($id_logframe) {
            $logframes = $this->findLogframesById($id_logframe);
        } else if ($level) {
            $logframes = $this->logframeList($id_periode, $level);
        }


         // final processing: populate
        $logframes = $this->populateLogframes($logframes, $id_instansi);
        $triwulan = $this->triwulanNow();

        $response = [
            'id_triwulan' => intval($triwulan['id_periode']) === intval($id_periode)
                ? $triwulan['id_triwulan']
                : null,
            'started_at' => intval($triwulan['id_periode']) === intval($id_periode)
                ? $triwulan['started_at']
                : null,
            'ended_at' => intval($triwulan['id_periode']) === intval($id_periode) 
                ? $triwulan['ended_at']
                : null,
            'verification_started_at' => intval($triwulan['id_periode']) === intval($id_periode)
                ? $triwulan['verification_started_at']
                : null,
            'verification_ended_at' => intval($triwulan['id_periode']) === intval($id_periode) 
                ? $triwulan['verification_ended_at']
                : null,
            'logframes' => $logframes
        ];

        // is now the reporting time or verifying time
        $now = new DateTime(date('Y-m-d H:i:s'));
        $started_at = $response['started_at'] ? new DateTime($response['started_at']) : null;
        $ended_at = $response['ended_at'] ? new DateTime($response['ended_at']) : null;
        $verification_started_at = $response['verification_started_at'] ? 
            new DateTime($response['verification_started_at']) : null;
        $verification_ended_at = $response['verification_ended_at'] ? 
            new DateTime($response['verification_ended_at']) : null;

        $response['is_reporting_time'] = $started_at <= $now && $now <= $ended_at;
        $response['is_verifying_time'] = $verification_started_at <= $now 
            && $now <= $verification_ended_at;

        return new SimpleResponse($response);
    }

    // StranaspkLogframeController
    public function getLogFrameIndex() {
        // $this->checkIsAuthenticated();
        $getParams = $this->request->get();
        $params = $this->validate(
            $getParams,
            [
                'id_periode' => ['validators' => ['required', 'digit']],
                'id_instansi' => ['validators' => ['digit']],
                'id_logframe' => ['validators' => ['digit']],
                'level' => ['validators' => ['digit']],
                'limit' => ['validators' => ['digit']],
                'offset' => ['validators' => ['digit']],
            ]
        );
        $id_periode = $params['id_periode'];
        $id_instansi = $params['id_instansi'] ?? null;
        $id_logframe = $params['id_logframe'] ?? null;
        $level = $params['level'] ?? null;
        if($level == 4){
            //menampilkan milestone, level 4 = milestone
            $logframes = $this->getLogframeLevel4($id_periode, $level);
        }else{
            // menampilkan output, level 3 = output, level 2 = Aksi
            $logframes = $this->getLogframeLevel3($id_periode, $level);
        }
        
        // $instansi = $this->getInstansi(20005);
        
        return new SimpleResponse($logframes);
    }

    public function searchLogframes(){
        $stores = new SleekDbStore('logframe');
        $instansi = new SleekDbStore('instansi');
        $getParams = $this->request->getJsonRawBody(true);
        $myParams = $this->validate(
            $getParams,
            [
              'id_logframe' => ['validators' => ['required']],
            ]
        );
        $logframes = $stores->findBy([
            ['id_logframe','like', "%" . $myParams['id_logframe'] . "%"],
            function($logframe){
                return !isset($logframe['is_deleted']);
            }
        ],
        [
            "_id" => "asc", "id_logframe" => "asc" // urut bds _id dan id_logframe
        ]);
        for($i=0; $i<count($logframes); $i++){
            for($j=0; $j<count($logframes[$i]['id_instansi']); $j++){
                $logframes[$i]['id_instansi'][$j] = 
                $instansi->findBy(
                    ['id_instansi','=',$logframes[$i]['id_instansi'][$j]],
                    ['name' => 'asc']);
            }
        }
        return new SimpleResponse($logframes);
    }

    public function getAllInstansi(){
        $store = new SleekDbStore('instansi');
        $allInstansi = $store->findAll();
        return new SimpleResponse($allInstansi);
    }

    public function deleteLogFrameByID(){
        // $this->checkIsAuthenticated();
        $stores = new SleekDbStore('logframe');
        $getParams = $this->request->getJsonRawBody(true);
        $myParams = $this->validate(
            $getParams,
            [
              'id' => ['validators' => ['required']]
            ]
        );
        $id = $myParams['id'];
        $stores->updateById($id, ["is_deleted" => true]);
        return new SimpleResponse($stores); 
    }

    public function postNewLogFrame(){
        // $this->checkIsAuthenticated();
        $stores = new SleekDbStore('logframe');
        $getParams = $this->request->getJsonRawBody(true);
        $myParams = $this->validate(
            $getParams,
            [
              'id_logframe' => ['validators' => ['required']],
              'id_parent' => ['validators' => ['required']],
              'bobot_milestone' => ['validators' => ['required']],
              'target' => ['validators' => ['required']],
              'long_name' => ['validators' => ['required']],
              'short_name' => ['validators' => ['required']],
              'id_instansi' => ['validators' => ['required']],
            ]
        );
        $id_logframe = $myParams['id_logframe'];
        $id_parent = $myParams['id_parent']['id'];
        $id_periode = 3;
        $bobot_milestone = $myParams['bobot_milestone'];
        $target = $myParams['target'];
        $long_name = $myParams['long_name'];
        $short_name = $myParams['short_name'];
        $level = 4;
        $id_instansi = [$myParams['id_instansi']['id_instansi']];
        $logframe = [
            'id_logframe' => $id_logframe,
            'id_parent' => $id_parent,
            'id_periode' => $id_periode,
            'level'=> $level,
            'name' => [
                'long' => $long_name,
                'short' => $short_name
            ],
            'weight' => $bobot_milestone,
            'target' => $target,
            'id_instansi' => $id_instansi
        ];
        // Insert the data.
        $data = $stores->insert($logframe);
        return new SimpleResponse($data);
    }

    public function postInstansiByMilestone(){
        $getParams = $this->request->getJsonRawBody(true);
        $myParams = $this->validate(
            $getParams,
            [
              'id_logframe' => ['validators' => ['required']],
              'id_instansi' => ['validators' => ['required']],
            ]
        );
        $store = new SleekDbStore('logframe');
        // retrieve a logframe
        $logframe = $store->findById($myParams['id_logframe']);
        // update logframe
        $logframe["id_instansi"] = $myParams['id_instansi'];  
        $store->update( $logframe ); 
    }


    public function updateLogFrame(){
        // $this->checkIsAuthenticated();
        $stores = new SleekDbStore('logframe');
        $getParams = $this->request->getJsonRawBody(true);
        $myParams = $this->validate(
            $getParams,
            [
              'id_logframe' => ['validators' => ['required']],
              'bobot_milestone' => ['validators' => ['required']],
              'target' => ['validators' => ['required']],
              'long_name' => ['validators' => ['required']],
              'short_name' => ['validators' => ['required']],
              'id'=> ['validators' => ['required']],
            ]
        );
        $id = $myParams['id'];
        $id_logframe = $myParams['id_logframe'];
        $bobot_milestone = $myParams['bobot_milestone'];
        $target = $myParams['target'];
        $long_name = $myParams['long_name'];
        $short_name = $myParams['short_name'];
        $logframe = $stores->findById($id);
        // input logframe
        $logframe['id_logframe'] = $id_logframe;
        $logframe['name']['long'] = $long_name;
        $logframe['name']['short'] = $short_name;
        $logframe['weight'] = $bobot_milestone;
        $logframe['target'] = $target;
        // update data
        $data = $stores->update($logframe);
        return new SimpleResponse($data); 
    }

    private function getLogframeLevel4($id_periode, $level){
        // $this->checkIsAuthenticated();
        $stores = new SleekDbStore('logframe');
        $instansi = new SleekDbStore('instansi');
        $logframes = $stores->findBy([
            ['id_periode', '=', intval($id_periode)],
            ['level', '=', intval($level)],
            function($logframe){
                return !isset($logframe['is_deleted']);
            }
        ],
        [
            "_id" => "asc", "id_logframe" => "asc" // urut bds _id dan id_logframe
        ]);
        for($i=0; $i<count($logframes); $i++){
            for($j=0; $j<count($logframes[$i]['id_instansi']); $j++){
                $logframes[$i]['id_instansi'][$j] = 
                $instansi->findBy(
                    ['id_instansi','=',$logframes[$i]['id_instansi'][$j]],
                    ['name' => 'asc']);
            }
        }
        return $logframes;
        $triwulan = $this->triwulanNow();

        $response = [
            'id_triwulan' => intval($triwulan['id_periode']) === intval($id_periode)
                ? $triwulan['id_triwulan']
                : null,
            'started_at' => intval($triwulan['id_periode']) === intval($id_periode)
                ? $triwulan['started_at']
                : null,
            'ended_at' => intval($triwulan['id_periode']) === intval($id_periode) 
                ? $triwulan['ended_at']
                : null,
            'verification_started_at' => intval($triwulan['id_periode']) === intval($id_periode)
                ? $triwulan['verification_started_at']
                : null,
            'verification_ended_at' => intval($triwulan['id_periode']) === intval($id_periode) 
                ? $triwulan['verification_ended_at']
                : null,
            'logframes' => $logframes
        ];

        // is now the reporting time or verifying time
        $now = new DateTime(date('Y-m-d H:i:s'));
        $started_at = $response['started_at'] ? new DateTime($response['started_at']) : null;
        $ended_at = $response['ended_at'] ? new DateTime($response['ended_at']) : null;
        $verification_started_at = $response['verification_started_at'] ? 
            new DateTime($response['verification_started_at']) : null;
        $verification_ended_at = $response['verification_ended_at'] ? 
            new DateTime($response['verification_ended_at']) : null;

        $response['is_reporting_time'] = $started_at <= $now && $now <= $ended_at;
        $response['is_verifying_time'] = $verification_started_at <= $now 
            && $now <= $verification_ended_at;

        return new SimpleResponse($response);
    }

    private function getLogframeLevel3($id_periode, $level){
        // $this->checkIsAuthenticated();
        $stores = new SleekDbStore('logframe');
        $instansi = new SleekDbStore('instansi');
        $logframes = $stores->findBy([
            ['id_periode', '=', intval($id_periode)],
            ['level', '=', intval($level)],
            function($logframe){
                return !isset($logframe['is_deleted']);
            }
        ],
        [
            "_id" => "asc", "id_logframe" => "asc" // urut bds _id dan id_logframe
        ]);
        return $logframes;
    }

    // Function untuk mengembalikan options pencarian:
    // daftar instansi yang terlibat dalam Stranas PK, atau
    // daftar aksi/output/logframe
    public function getSearchOptions() {
        // $this->checkIsAuthenticated();
        $getParams = $this->request->get();
        $params = $this->validate(
            $getParams,
            [
                'id_periode' => ['validators' => ['required', 'digit']],
                'keyword' => ['validators' => ['required']],
                'limit' => ['validators' => ['digit']],
                'offset' => ['validators' => ['digit']],
            ]
        );
        $id_periode = $params['id_periode'];
        $keyword = $params['keyword']; // $keyword bisa berarti instansi maupun aksi/output/milestones
        $limit = $params['limit'] ?? null;
        $offset = $params['offset'] ?? null;
        
        // pencarian by aksi/output/milestones
        $searchOptions = $this->searchOptionsByKeyword($id_periode, $keyword, $limit, $offset);
        return new SimpleResponse($searchOptions);
    }

    private function searchOptionsByKeyword($id_periode, $keyword, $limit, $offset) {
        
        $output = [];

        // 1. cari dulu dari instansi / K/L
        $strWhere = 'Instansi.nama ilike :keyword:';
        $bind['keyword'] = '%' . $keyword . '%';
        $result = Instansi::find(
            [
                $strWhere,
                "bind" => $bind,
                "limit" => $limit,
                "offset" => $offset
            ]
        )->toArray();
        $instansiList = [];
        foreach($result as $row) {
            $instansiList[] = [
              "id_instansi" => $row['id'],
              "nama" => $row['nama']
            ];
        }
        
        if (sizeof($instansiList) === intval($limit)) {
            return $instansiList;
        }

        // lanjutkan dengan pencarian pada logframes
        $logframeLimit = intval($limit) - sizeof($instansiList);
        $logframeOffset = 0;
        
        if (sizeof($instansiList) === 0) {
            // tidak ada instansi di atas yang dipilih, berapa offset untuk logframe?
            $countInstansi = Instansi::count(
                [
                    $strWhere,
                    "bind" => $bind
                ]
            );
            $logframeOffset = intval($offset) - $countInstansi; // untuk diperoleh offset logframe
        } 

        // 2. Bila output belum mencapai target cari logframe berdasarkan keyword
        $store = new SleekDbStore('logframe');
        
        $queryBuilder = $store->createQueryBuilder()
            ->where(
                [
                    ["id_periode", "=", intval($id_periode)],
                    [
                        ["level", "=", 2], // aksi
                        "OR",
                        ["level", "=", 3], // output
                        "OR",
                        ["level", "=", 4] // milestone
                    ],
                    function($logframe){
                        return !isset($logframe['is_deleted']);
                    }
                ]
            )
            ->search(["id_logframe", "name.long", "name.short"], $keyword)
            ->select(['id_logframe', 'level', 'id_periode', 'name.short'])
            ->orderBy(["_id" => "asc", "id_logframe" => "asc"])
            ->limit($logframeLimit)
            ->skip($logframeOffset);
        $logframeList = $queryBuilder
            ->getQuery()
            ->fetch();
        return array_merge($instansiList, $logframeList);
    }

    private function findLogframesByInstansi($id_periode, $id_instansi) {
        // mencari logframe by id_instansi
        $store = new SleekDbStore('logframe');
        $logframes = [];
        return $store->findBy([
            ['id_periode', '=', intval($id_periode)],
            ['id_instansi', 'CONTAINS', intval($id_instansi)],
            function($logframe){
                return !isset($logframe['is_deleted']);
            }
        ],[
            "_id" => "asc", "id_logframe" => "asc" // urut bds _id dan id_logframe
        ]);
    }
    
    private function findParentLogframe($id_parent) {
        // get a logframe's parent
        $store = new SleekDbStore('logframe');
        $parentLogframe = $store->findById(intval($id_parent));
        if ($parentLogframe && (array_key_exists('id_parent', $parentLogframe))) {
            $parentLogframe['parent'] = $this->findParentLogframe($parentLogframe['id_parent']);
        }
        return $parentLogframe;
    }

    private function findLogframesById($id_logframe) {
        // mencari logframe by id_logframe
        // pada tahun 2023: itu berarti level milestone
        // sampai level logframe yang mengandung id_instansi
        $store = new SleekDbStore('logframe');

        // get the initial logframe
        $logframe = $store->findById(intval($id_logframe));        
        if (array_key_exists('id_instansi', $logframe)) {
            if (array_key_exists('id_parent', $logframe)) {
                $logframe['parent'] = $this->findParentLogframe($logframe['id_parent']);
            }
            $logframe['instansi'] = $this->selectInstansi($logframe['id_instansi']);
            return [$logframe]; // langsung memperoleh leaf ,@2023 -> milestone
        } else {
            $logframe['children'] = $this->findLogframeChildren($logframe['_id']);
            $leaves = $this->pickLogframeLeaves($logframe);
            return $leaves;
        }
    }

    private function pickLogframeLeaves($logframe) {
        if (!array_key_exists('children', $logframe)) {
            // leaf already

            // <CAUTION> pls make more efficient
            $logframe['parent'] = $this->findParentLogframe($logframe['id_parent']);
            // pls make more efficient </CAUTION>

            return $logframe;
        }
        else {
            $childNodes = [];
            foreach ($logframe['children'] as $child) {
                $leaf = $this->pickLogframeLeaves($child);
                if (array_key_exists('id_instansi', $leaf)) {
                    $childNodes[] = $leaf;
                } else {
                    // non-leaf
                    $childNodes = array_merge($childNodes, $leaf);
                }
            }
            return $childNodes;
        }
    }

    private function findLogframeChildren($id_logframe) {
        // find logframe children
        $store = new SleekDbStore('logframe');
        $children = $store->findBy([
            [
                'id_parent', '=', intval($id_logframe)
            ],
            function($logframe){
                return !isset($logframe['is_deleted']);
            }
        ], 
        [
            "_id" => "asc"
        ]);        
        for($i = 0; $i < sizeof($children); $i++) {
            if (array_key_exists('id_instansi', $children[$i])) continue; // leaf already
            $children[$i]['children'] = $this->findLogframeChildren($children[$i]['_id']);
        }
        return $children;
    }

    private function populateLogframes($logframes, $id_instansi) {
        // get the children's parents
        for ($i = 0; $i < sizeof($logframes); $i++) {
            if (!array_key_exists('id_parent', $logframes[$i])) continue;
            $logframes[$i]['parent'] = $this->findParentLogframe($logframes[$i]['id_parent']);
            if (array_key_exists('id_instansi', $logframes[$i])) {
                $logframes[$i]['instansi'] = $this->selectInstansi($logframes[$i]['id_instansi']);
                
                // instansi dengan id_instansi harus yang paling atas
                if ($id_instansi) {
                    $logframes[$i]['instansi'] = $this->putFirstInstansi($id_instansi, $logframes[$i]['instansi']);
                }
                unset($logframes[$i]['id_instansi']);
            }

            if (array_key_exists('reports', $logframes[$i])
                && is_array($logframes[$i]['reports'])
                && sizeof($logframes[$i]['reports']) > 0) {
                //
                $logframes[$i]["reports"] = $this->findAndPopulateReports($logframes[$i]["reports"]);
            }
            if (array_key_exists('verifications', $logframes[$i])
                && is_array($logframes[$i]['verifications'])
                && sizeof($logframes[$i]['verifications']) > 0) {
                $logframes[$i]["verifications"] = $this->findVerifications($logframes[$i]["verifications"]);
            }
        }
        // urutkan berdasarkan id_logframe
        usort($logframes, function ($a, $b) {
            return strcmp($a["id_logframe"], $b["id_logframe"]);
        });
        return $logframes;
    }

    private function selectUmUserInstansi($um_id) {
        $um_user = UmUser::findFirst(
            [
                "um_id = :um_id: and is_active = true",
                "bind" => [
                    "um_id" => $um_id
                ]
            ]
        );
        $instansi = null;
        if ($um_user && $um_user->id_instansi) {
            $instansi = Instansi::findFirst(
                [
                    "id = :id_instansi: and deleted_at is null",
                    "bind" => [
                        "id_instansi" => $um_user->id_instansi
                    ]
                ]
            );
        }
        return [
            "id" => $um_id,
            "nama" => $um_user ? $um_user->nama : null,
            "id_instansi" => $instansi ? $instansi->id : null,
            "nama_instansi" => $instansi ? $instansi->nama : null
        ];
    }

    private function selectUmUser($um_id) {
        // Mengambil data id dan nama dari um_user
        $um_user = UmUser::findFirst(
            [
                "um_id = :um_id: and is_active = true",
                "bind" => [
                    "um_id" => $um_id
                ]
            ]
        );
        return [
            "id" => $um_id,
            "nama" => $um_user ? $um_user->nama : null,
        ];
    }

    private function selectInstansi($id_instansi_list) {
        // $id_instansi: array of id_instansi
        $output = [];
        $instansi_list = Instansi::find([
            'conditions' => 'id in ({ids:array}) AND deleted_at is NULL',
            'bind' => [
                'ids' => array_values($id_instansi_list)
            ]
        ]);
        foreach ($instansi_list as $key => $instansi) {
            $output[] = [
                'id' => $instansi->id,
                'nama' => $instansi->nama,
            ];
        }
        return $output;
    }

    public function report() {
        $this->checkScopes(['stranas.answer']);
        $reportAllowedVar = getenv("STRANASPK_REPORT_AND_VERIFICATION_ALLOWED");
        $reportAllowed = $reportAllowedVar ? filter_var($reportAllowedVar, FILTER_VALIDATE_BOOLEAN) : true;
        if (!$reportAllowed) {
            throw new CustomErrorException(BaseResponse::INVALID_REQUEST, 'Form report sedang ditutup. Admin akan mengabari lebih lanjut');
        }
        $postParams = $this->request->getJsonRawBody(true);
        $params = $this->validate(
            $postParams,
            [
                'report' => ['validators' => []],
            ]
        );
        
        // get the document
        $logframeStore = new SleekDbStore('logframe');
        if (array_key_exists('id_logframe', $params['report'])) {
            $logframeDocument = $logframeStore->findById($params['report']['id_logframe']);
            // setiap report harus memiliki id_logframe
            if (is_null($logframeDocument)) {
                throw new CustomErrorException(BaseResponse::INVALID_REQUEST, 'Document tidak ditemukan');
            }
        }        

        // assign the report's extra important info
        $report = $params['report'];
        $um_id = $this->shared->user->um_id;
        if (!array_key_exists('_id', $report)) {
            // new report
            $report["created_by"] = $um_id;
            $report["created_at"] = $this->now();
        } else {
            $report["updated"][] = [
                "updated_by" => $um_id,
                "updated_at" => $this->now()
            ];
        }
        
        // handle the document
        $reportStore = new SleekDbStore('reports');
        if (!array_key_exists('_id', $report)) {
            // insert
            $report = $reportStore->insert($report);
            $logframeDocument["reports"][] = $report["_id"];
            $logframeStore->update($logframeDocument);
        } else {
            // update
            $report['created_by'] = $report['created_by']['id'];
            unset($report['instansi']);
            $reportStore->update($report);
        }
        
        // return success
        $report["created_by"] = $this->selectUmUserInstansi($report["created_by"]);
        return new SimpleResponse(["report" => $report]);
    }

    public function deleteReport() {
        $this->checkScopes(['stranas.answer']);
        $reportAllowedVar = getenv("STRANASPK_REPORT_AND_VERIFICATION_ALLOWED");
        $reportAllowed = $reportAllowedVar ? filter_var($reportAllowedVar, FILTER_VALIDATE_BOOLEAN) : true;
        if (!$reportAllowed) {
            throw new CustomErrorException(BaseResponse::INVALID_REQUEST, 'Form report sedang ditutup. Admin akan mengabari lebih lanjut');
        }
        $postParams = $this->request->getJsonRawBody(true);
        $params = $this->validate(
            $postParams,
            [
                '_id' => ['validators' => ['digit', 'required']],
            ]
        );
        $reportStore = new SleekDbStore('reports');
        $report = $reportStore->findById(intval($params['_id']));
        $report['is_deleted'] = true;
        $um_id = $this->shared->user->um_id;
        $report['deleted'] = [
            "deleted_by" => $um_id,
            "deleted_at" => $this->now()
        ];
        $reportStore->update($report);
        return new SimpleResponse(true);
    }

    public function deleteVerification() {
        $this->checkScopes(['stranas.verify']);
        $verifyAllowedVar = getenv("STRANASPK_REPORT_AND_VERIFICATION_ALLOWED");
        $verifyAllowed = $verifyAllowedVar ? filter_var($verifyAllowedVar, FILTER_VALIDATE_BOOLEAN) : true;
        if (!$verifyAllowed) {
            throw new CustomErrorException(BaseResponse::INVALID_REQUEST, 'Form verifikasi sedang ditutup. Admin akan mengabari lebih lanjut');
        }
        $postParams = $this->request->getJsonRawBody(true);
        $params = $this->validate(
            $postParams,
            [
                '_id' => ['validators' => ['digit', 'required']],
            ]
        );
        $verificationStore = new SleekDbStore('verifications');
        $verification = $verificationStore->findById(intval($params['_id']));
        $verification['is_deleted'] = true;
        $um_id = $this->shared->user->um_id;
        $verification['deleted'] = [
            "deleted_by" => $um_id,
            "deleted_at" => $this->now()
        ];
        $verificationStore->update($verification);
        
        // dapatkan data untuk mengupdate dashboard secara realtime
        $logframeStore = new SleekDbStore('logframe');
        $logframe = $logframeStore->findById($verification['id_logframe']);
        
        // empat data yang dibutuhkan untuk update dashboard secara realtime
        $id_logframe = $logframe['id_logframe'];
        $id_periode = $logframe['id_periode'];
        $id_triwulan = $verification['id_triwulan'];
        $value = 0;
        $this->updateDashboardByIdLogframe($id_logframe, $id_periode, $id_triwulan, $value);

        return new SimpleResponse(true);
    }

    public function verify() {
        $this->checkScopes(['stranas.verify']);
        $verifyAllowedVar = getenv("STRANASPK_REPORT_AND_VERIFICATION_ALLOWED");
        $verifyAllowed = $verifyAllowedVar ? filter_var($verifyAllowedVar, FILTER_VALIDATE_BOOLEAN) : true;
        if (!$verifyAllowed) {
            throw new CustomErrorException(BaseResponse::INVALID_REQUEST, 'Form verifikasi sedang ditutup. Admin akan mengabari lebih lanjut');
        }
        $postParams = $this->request->getJsonRawBody(true);

        $params = $this->validate(
            $postParams,
            [
                'verification' => ['validators' => []],
            ]
        );

        // get the document
        $logframeStore = new SleekDbStore('logframe');
        $id_logframe = null;
        $id_periode = null;
        if (array_key_exists('id_logframe', $params['verification'])) {
            $logframe = $logframeStore->findById($params['verification']['id_logframe']);
            $id_logframe = $logframe['id_logframe'];
            $id_periode = $logframe['id_periode'];
            // setiap verification harus memiliki id_logframe
            if (is_null($logframe)) {
                throw new CustomErrorException(BaseResponse::INVALID_REQUEST, 'Document tidak ditemukan');
            }
        }        

        // assign the verification's extra important info
        $verification = $params['verification'];
        $um_id = $this->shared->user->um_id;
        if (!array_key_exists('_id', $verification)) {
            // new verification
            $verification["created_by"] = $um_id;
            $verification["created_at"] = $this->now();
        } else {
            $verification["updated"][] = [
                "updated_by" => $um_id,
                "updated_at" => $this->now()
            ];
        }
        
        // handle the document
        $verificationStore = new SleekDbStore('verifications');
        if (!array_key_exists('_id', $verification)) {
            // insert
            $verification = $verificationStore->insert($verification);
            $logframe["verifications"][] = $verification["_id"];
            $logframeStore->update($logframe);
        } else {
            // update
            $verification['created_by'] = $verification['created_by']['id'];
            $verificationStore->update($verification);
        }
        $verification["created_by"] = $this->selectUmUser($verification["created_by"]);

        $id_triwulan = $verification['id_triwulan'];
        $value = is_null($verification['score']) ? 0 : $verification['score']['value'];
        $this->updateDashboardByIdLogframe($id_logframe, $id_periode, $id_triwulan, $value);

        return new SimpleResponse(["verification" => $verification]);
    }

    private function now() {
        $dt = new DateTime();
        $dt->setTimezone(new DateTimeZone('Asia/Jakarta'));
        $dt->setTimestamp(round(microtime(true)));
        return $dt->format('Y-m-d H:i:s');
    }

    private function isTarget($logframe) {
        return array_key_exists("target", $logframe);
    }

    private function findProgressTriwulanIndex($progress, $id_triwulan) {
        foreach($progress as $key => $item) {
            if (array_key_exists('id_triwulan', $item) &&
                $item['id_triwulan'] === $id_triwulan)
                return $key;
        }
        return null;
    }

    private function triwulanNow() {
        // private method yang mengembalikan triwulan berjalan
        $now = date('Y-m-d H:i:s');
        $triwulan = StranasNewPeriodeTriwulan::findFirst([
            'conditions' => '{now} >= started_at AND {now} <= verification_ended_at',
            'bind' => [
                'now' => $now
            ]
        ]);
        
        return [
            'id_periode' => $triwulan ? $triwulan->id_periode : null,
            'id_triwulan' => $triwulan ? $triwulan->id_triwulan : null,
            'started_at' => $triwulan ? $triwulan->started_at : null,
            'ended_at' => $triwulan ? $triwulan->ended_at : null,
            'verification_started_at' => $triwulan ? $triwulan->verification_started_at : null,
            'verification_ended_at' => $triwulan ? $triwulan->verification_ended_at : null,
        ];
    }

    private function putFirstInstansi($id_instansi, array $instansiList) {
        // menaruh instansi dengan $id_instansi sebagai yang teratas
        for($i = 0; $i < sizeof($instansiList); $i++) {
            if ($instansiList[$i]['id'] === intval($id_instansi)) {
                // swap
                $temp = $instansiList[0];
                $instansiList[0] = $instansiList[$i];
                $instansiList[$i] = $temp;
                return $instansiList;
            }
        }
    }

    private function findAndPopulateReports(array $id_reports) {
        // mengambil dan mengklasifikasikan seluruh reports dari koleksi 'reports' dengan $id_reports sebagai keys
        // $id_reports adalah array of integer, yang mewakili _id dari koleksi 'reports'
        $store = new SleekDbStore('reports');
        $reports = [];
        foreach($id_reports as $id) {
            $report = $store->findById($id);
            if (is_null($report) || !$report) continue;
            if (array_key_exists('is_deleted', $report)) continue; // soft deleted reports are excluded
            if ($report && array_key_exists('id_instansi', $report)) {
                $report['instansi'] = $this->selectInstansi([$report['id_instansi']])[0];
            }
            if ($report && array_key_exists('created_by', $report)) {
                $report['created_by'] = $this->selectUmUserInstansi($report['created_by']);
            }
            if ($report && array_key_exists('id_triwulan', $report)) {
                // klasifikasi berdasarkan triwulan
                $id_triwulan = $report['id_triwulan'];
                if (!array_key_exists($id_triwulan, $reports)) {
                    $reports[$id_triwulan] = [$report];
                } else {
                    $reports[$id_triwulan][] = $report;
                }
            }
        }
        return $reports;
    }

    private function findVerifications(array $id_verifications) {
        // mengambil seluruh verifications dari koleksi
        // 'verifications' dengan $id_verifications sebagai keys
        $store = new SleekDbStore('verifications');
        $instansiStore = new SleekDbStore('instansi');
        $verifications = [];
        foreach($id_verifications as $id) {
            $verification = $store->findById($id);
            if (!$verification) continue; // not found
            if (array_key_exists('is_deleted', $verification)) continue; // soft deleted verifications are excluded
            if (!array_key_exists('id_triwulan', $verification)) continue; // harus ada id_triwulan
            if (array_key_exists('created_by', $verification)) {
                $verification['created_by'] = $this->selectUmUser($verification['created_by']);
            }
            $verifications[$verification['id_triwulan']] = $verification;
        }
        return $verifications;
    }

    private function updateDashboardByIdLogframe($id_logframe, $id_periode, $id_triwulan, $score) {
        // mengupdate dashboard berdasarkan id_logframe
        $dashboardStore = new SleekDbStore('dashboard');
        $logframeStore = new SleekDbStore('logframe');
        $instansiStore = new SleekDbStore('instansi');
        $daftarInstansi = $logframeStore->findBy(["id_logframe", "=", $id_logframe]);
        $jumlahInstansi = 0;
        // menghitung jumlah instansi pada logframe
        $jumlahInstansi = count($daftarInstansi[0]["id_instansi"]);
        $dashboardMilestone = $dashboardStore->findOneBy(
            [
                ['id_logframe', '=', $id_logframe],
                ['id_periode', '=', intval($id_periode)],
                ['id_triwulan', '=', intval($id_triwulan)],
                function($dashboard){
                    return !isset($dashboard['is_deleted']);
                }
            ]
        );
        if (is_null($dashboardMilestone)) {
            return;
        }
        
        $logframes = $logframeStore->findBy(["id_logframe", "=", $id_logframe]);
        $daftarInstansiPadaLogframe = [];
        $bobotOutput = 0;
        foreach($logframes as $logframe) {
            $daftarInstansi = [];
            // jika ada id_parent pada logframe
            if(array_key_exists('id_parent', $logframe)) {
                // cari id parent
                $parentLogframe = $this->findParentLogframe($logframe["id_parent"]);
                // simpan bobot output
                $bobotOutput = $parentLogframe["weight"];
            }
            // jika ada id instansi pada logframe
            if(array_key_exists('id_instansi', $logframe)) {
                // collect id instansi
                foreach($logframe["id_instansi"] as $id_instansi) {
                    $instansi = $instansiStore->findAll();
                    // collection instansi
                    foreach($instansi as $ins) {
                        // jika id instansi pada instansi = $id instansi yang ada pada logframe
                        if($ins["id_instansi"] == $id_instansi) {
                            // simpan daftar instansi 
                            $daftarInstansiPadaLogframe[$ins["id_instansi"]] = $ins["nama"];
                        }
                    }
                }
            }
        }
        // ambil daftar instansi pada milestone
        $instansiMerge = [];
        foreach($daftarInstansiPadaLogframe as $key => $instansi) {
            $id_instansi = $key;
            $nama = $instansi;
            $instansiMerge[] = ['id_instansi' => $id_instansi, 'nama' => $nama];
        }
        // rumus milestone kontribusi dan milestone mandiri
        $dashboardMilestone['nilai_target_milestone_kontribusi'] = $dashboardMilestone["weight"] / 100 * 1 / $jumlahInstansi * $jumlahInstansi;
        $dashboardMilestone['nilai_aktual_milestone_kontribusi'] = $dashboardMilestone["weight"] / 100 * 1 / $jumlahInstansi * $score / 100 * $jumlahInstansi;
        $dashboardMilestone['nilai_target_milestone_mandiri'] = $dashboardMilestone['nilai_target_milestone_kontribusi'] / $dashboardMilestone['nilai_target_milestone_kontribusi'];
        $dashboardMilestone['nilai_aktual_milestone_mandiri'] = $dashboardMilestone['nilai_aktual_milestone_kontribusi'] / $dashboardMilestone['nilai_target_milestone_kontribusi'];

        // rumus milestone kld
        $nilaiInstansiMilestone['kld_target_milestone_kontribusi'] = $dashboardMilestone["weight"] / 100 * 1 / $jumlahInstansi;
        $nilaiInstansiMilestone['kld_aktual_milestone_kontribusi'] = $dashboardMilestone["weight"] / 100 * 1 / $jumlahInstansi * $score / 100;
        $nilaiInstansiMilestone['kld_target_milestone_mandiri'] = $nilaiInstansiMilestone['kld_target_milestone_kontribusi'] / $nilaiInstansiMilestone['kld_target_milestone_kontribusi'];
        $nilaiInstansiMilestone['kld_aktual_milestone_mandiri'] = $nilaiInstansiMilestone['kld_aktual_milestone_kontribusi'] / $nilaiInstansiMilestone['kld_target_milestone_kontribusi'];
        
        // rumus output kld
        $nilaiInstansiMilestone['kld_target_output_kontribusi'] = $bobotOutput / 100 * $dashboardMilestone["weight"] / 100 * 1 / $jumlahInstansi;
        $nilaiInstansiMilestone['kld_aktual_output_kontribusi'] = $nilaiInstansiMilestone['kld_target_output_kontribusi'] * $score / 100;
        $nilaiInstansiMilestone['kld_target_output_mandiri'] = $nilaiInstansiMilestone['kld_target_output_kontribusi'] / $nilaiInstansiMilestone['kld_target_output_kontribusi'];
        $nilaiInstansiMilestone['kld_aktual_output_mandiri'] = $nilaiInstansiMilestone['kld_aktual_output_kontribusi'] / $nilaiInstansiMilestone['kld_target_output_kontribusi'];
        
        // rumus aksi kld
        $nilaiInstansiMilestone['kld_target_aksi_kontribusi'] = 1 / 15 * $bobotOutput / 100 * $dashboardMilestone["weight"] / 100 * 1 / $jumlahInstansi;
        // $aktualKontribusi = $nilaiInstansiMilestone['kld_target_aksi_kontribusi'] * $score / 100;
        // $nilaiInstansiMilestone['kld_aktual_aksi_kontribusi'] = number_format($aktualKontribusi, 19);
        $nilaiInstansiMilestone['kld_aktual_aksi_kontribusi'] = $nilaiInstansiMilestone['kld_target_aksi_kontribusi'] * $score / 100;
        $nilaiInstansiMilestone['kld_target_aksi_mandiri'] = $nilaiInstansiMilestone['kld_target_aksi_kontribusi'] / $nilaiInstansiMilestone['kld_target_aksi_kontribusi'];        
        $nilaiInstansiMilestone['kld_aktual_aksi_mandiri'] = $nilaiInstansiMilestone['kld_aktual_aksi_kontribusi'] / $nilaiInstansiMilestone['kld_target_aksi_kontribusi'];
        
        // masukkan nilai milestone dan output kld kedalam setiap instansi
        foreach($instansiMerge as $key => $instansi) {
            $mergeData = array_merge($instansi, $nilaiInstansiMilestone);
            $instansiAndScoreMerge[] = $mergeData;
        }
        $dashboardMilestone['nilai_kld'] = $instansiAndScoreMerge;
        $dashboardMilestone['score'] = $score;
        $dashboardStore->update($dashboardMilestone); // write to dashboard store

        // update ke output
        // ambil outputnya
        // ambil output dari daftar milestone. misalnya milestone 3.5.1 dan 3.5.2 maka output nya adalah 3.5
        // cari berdasarkan id parent pada milestone
        $daftarLogframePadaOutput = $dashboardStore->findBy(["id_logframe", "=", $parentLogframe["id_logframe"]]);
        $daftarInstansiPadaOutput = [];
        $sumNilaiTargetOutputKontribusi = [];
        $sumNilaiAktualOutputKontribusi = [];
        $sumNilaiTargetAksiKontribusi = [];
        $sumNilaiAktualAksiKontribusi = [];
        foreach($daftarLogframePadaOutput as $output) {
            if (array_key_exists('is_deleted', $output)) continue;
            if ($output["id_triwulan"] != $id_triwulan) continue;
            if ($output["id_children"]) {
                $idChildrenDariOutput = [];
                // update data nilai pada output ambil dulu semua milestone yang ada pada output
                // mengambil children dari output misalnya output 3.3 maka childrennya 3.3.1 dan 3.3.2
                foreach($output["id_children"] as $key => $idChildren) {
                    $idChildrenDariOutput[] = $idChildren;
                }
                $childrenDariOutput = $dashboardStore->findBy(["_id", "IN", $idChildrenDariOutput]);
                $childrens = [];
                foreach($childrenDariOutput as $childrenOutput) {
                    $childrens[] = $childrenOutput;
                }
                $nilaiTargetMilestonesKontribusi = [];
                $nilaiAktualMilestonesKontribusi = [];
                $nilaiKldAllMilestone = [];
                foreach($childrens as $child) {
                    if(isset($child["nilai_aktual_milestone_kontribusi"])) {
                        $nilaiAktualMilestonesKontribusi[] = $child["nilai_aktual_milestone_kontribusi"];
                    }
                    if(isset($child["nilai_target_milestone_kontribusi"])) {
                        $nilaiTargetMilestonesKontribusi[] = $child["nilai_target_milestone_kontribusi"];
                    }
                    // cari id instansi dari output
                    if(isset($child["id_instansi"])) {
                        $idInstansiOutput[] = $child["id_instansi"];
                    }
                    // ambil nilai kld pada semua milestone
                    if(isset($child['nilai_kld'])) {
                        $nilaiKldAllMilestone[] = $child['nilai_kld'];
                    }
                }
                // print_r($nilaiKldAllMilestone);
                // merged array bersarang dari nilai kld semua milestone
                $nilaiKldMilestoneMerged = call_user_func_array('array_merge_recursive', $nilaiKldAllMilestone);
                // menjumlahkan nilai target output, aktual output, target aksi dan aktual aksi pada setiap milestone
                foreach($nilaiKldMilestoneMerged as $nilaiKld) {
                    $id_instansi = $nilaiKld['id_instansi'];

                    if(!isset($sumNilaiTargetOutputKontribusi[$id_instansi])) {
                        $sumNilaiTargetOutputKontribusi[$id_instansi] = $nilaiKld;
                    } else {
                        // penjumlahan kld target output kontribusi
                        $sumNilaiTargetOutputKontribusi[$id_instansi]['kld_target_output_kontribusi'] += $nilaiKld['kld_target_output_kontribusi'];
                    }

                    if(!isset($sumNilaiAktualOutputKontribusi[$id_instansi])) {
                        $sumNilaiAktualOutputKontribusi[$id_instansi] = $nilaiKld;
                    } else {
                        // penjumlahan kld aktual output kontribusi
                        $sumNilaiAktualOutputKontribusi[$id_instansi]['kld_aktual_output_kontribusi'] += $nilaiKld['kld_aktual_output_kontribusi'];
                    }

                    if(!isset($sumNilaiTargetAksiKontribusi[$id_instansi])) {
                        // penjumlahan kld target aksi kontribusi
                        $sumNilaiTargetAksiKontribusi[$id_instansi] = $nilaiKld;
                    } else {
                        $sumNilaiTargetAksiKontribusi[$id_instansi]['kld_target_aksi_kontribusi'] += $nilaiKld['kld_target_aksi_kontribusi'];                        
                    }

                    if(!isset($sumNilaiAktualAksiKontribusi[$id_instansi])) {
                        // penjumlahan kld target aksi kontribusi
                        $sumNilaiAktualAksiKontribusi[$id_instansi] = $nilaiKld;
                    } else {
                        $sumNilaiAktualAksiKontribusi[$id_instansi]['kld_aktual_aksi_kontribusi'] += $nilaiKld['kld_aktual_aksi_kontribusi'];                        
                    }
                }
                $jumlahNilaiAktualMilestone = array_sum($nilaiAktualMilestonesKontribusi);
                $jumlahNilaiTargetMilestone = array_sum($nilaiTargetMilestonesKontribusi);

                // cari instansi pada setiap output
                $collectIdInstansi = [];
                foreach($idInstansiOutput as $key => $value) {
                    if(is_array($value)) {
                        $collectIdInstansi = array_merge($collectIdInstansi, $value);
                    }
                }
                $findInstansiFromOutput = $instansiStore->findBy(["id_instansi", "IN", $collectIdInstansi]);
                foreach($findInstansiFromOutput as $instansi) {
                    $id_instansi = $instansi['id_instansi'];
                    $nama = $instansi['nama'];
                    $daftarInstansiPadaOutput[] = ['id_instansi' => $id_instansi, 'nama' => $nama];
                }
                // end instansi pada setiap output
            }
        }
        $nilaiTargetOutputKontribusi = [];
        $nilaiAktualOutputKontribusi = [];
        $jumlahNilaiAktualOutput = null;
        $jumlahNilaiTargetOutput = null; 
        // mengambil data aksi misalnya aksi 3 atau Aksi BO untuk menjumlajkan target output dan aktual output

        $dataOutputPadaAksi = $dashboardStore->findBy(["_id", "=", $childrenDariOutput[0]["id_parent"]]);

        $idAllOutputFromAksi = [];
        foreach($dataOutputPadaAksi as $output) {
            if (array_key_exists('is_deleted', $output)) continue;
            if ($output["id_triwulan"] != $id_triwulan) continue;
            if ($output["id_parent"]) {
                $getAksiDariOutput = $dashboardStore->findBy(["_id", "=", $output["id_parent"]]);
                if($getAksiDariOutput[0]["id_children"]) {
                    $idAllOutputFromAksi[] = $getAksiDariOutput[0]["id_children"];                   
                }
                $getAllOutputFromAksi = $dashboardStore->findBy(["_id", "IN", $idAllOutputFromAksi[0]]);
                foreach($getAllOutputFromAksi as $aksi) {
                    if(isset($aksi["nilai_aktual_output_kontribusi"])) {
                        $nilaiAktualOutputKontribusi[] = $aksi["nilai_aktual_output_kontribusi"];
                    }
                    if(isset($aksi["nilai_target_output_kontribusi"])) {
                        $nilaiTargetOutputKontribusi[] = $aksi["nilai_target_output_kontribusi"];
                    }
                }
            }
        }

        $jumlahNilaiAktualOutput = array_sum($nilaiAktualOutputKontribusi);
        $jumlahNilaiTargetOutput = array_sum($nilaiTargetOutputKontribusi);

        $dashboardOutput = $dashboardStore->findById($dashboardMilestone['id_parent']);
        $dashboardOutput["nilai_target_output_kontribusi"] = $bobotOutput / 100 * $jumlahNilaiTargetMilestone;
        $dashboardOutput["nilai_aktual_output_kontribusi"] = $bobotOutput / 100 * $jumlahNilaiAktualMilestone;
        if($dashboardOutput["nilai_target_output_kontribusi"] != 0) {
            $dashboardOutput["nilai_target_output_mandiri"] = $dashboardOutput["nilai_target_output_kontribusi"] / $dashboardOutput["nilai_target_output_kontribusi"];
        }
        if($dashboardOutput["nilai_aktual_output_kontribusi"] != 0) {
            $dashboardOutput["nilai_aktual_output_mandiri"] = $dashboardOutput["nilai_aktual_output_kontribusi"] / $dashboardOutput["nilai_target_output_kontribusi"];
        }
        // nilai instansi level output
        $nilaiInstansiLevelOutput['nilai_target_output_kontribusi'] = null;
        $nilaiInstansiLevelOutput['nilai_aktual_output_kontribusi'] = null;
        $nilaiInstansiLevelOutput['nilai_target_output_mandiri'] = 1;
        $nilaiInstansiLevelOutput['nilai_aktual_output_mandiri'] = null;
        $nilaiInstansiLevelOutput['nilai_target_aksi_kontribusi'] = null;
        $nilaiInstansiLevelOutput['nilai_aktual_aksi_kontribusi'] = null;
        $nilaiInstansiLevelOutput['nilai_target_aksi_mandiri'] = 1;
        $nilaiInstansiLevelOutput['nilai_aktual_aksi_mandiri'] = null;

        // masukkan nilai ke setiap instansi
        foreach($daftarInstansiPadaOutput as $key => $instansi) {
            $mergeData = array_merge($instansi, $nilaiInstansiLevelOutput);
            $instansiAndScoreOutput[] = $mergeData;
        }
        $dashboardOutput['nilai_kld'] = $instansiAndScoreOutput;

        // cari instansi apakah memiliki kld_target_output_kontribusi dan kld_aktual_output_kontribusi
        $targetKontribusiByInstansi = [];
        $aktualKontribusiByInstansi = [];
        $targetAksiKontribusiByInstansi = [];
        $aktualAksiKontribusiByInstansi = [];
        foreach ($sumNilaiTargetOutputKontribusi as $item) {
            $targetKontribusiByInstansi[$item['id_instansi']] = $item['kld_target_output_kontribusi'];
        }

        foreach($sumNilaiAktualOutputKontribusi as $item) {
            $aktualKontribusiByInstansi[$item['id_instansi']] = $item['kld_aktual_output_kontribusi'];
        }
        
        foreach($sumNilaiTargetAksiKontribusi as $item) {
            $targetAksiKontribusiByInstansi[$item['id_instansi']] = $item['kld_target_aksi_kontribusi'];
        }

        foreach($sumNilaiAktualAksiKontribusi as $item) {
            $aktualAksiKontribusiByInstansi[$item['id_instansi']] = $item['kld_aktual_aksi_kontribusi'];
        }

        // jika ya maka simpan kld_target_output_kontribusi ke dalam nilai_target_output_kontribusi
        foreach ($dashboardOutput['nilai_kld'] as &$instansi) {
            $id_instansi = $instansi['id_instansi'];
            if(isset($targetKontribusiByInstansi[$id_instansi])) {
                $instansi['nilai_target_output_kontribusi'] = $targetKontribusiByInstansi[$id_instansi];
            }

            if(isset($aktualKontribusiByInstansi[$id_instansi])) {
                $instansi['nilai_aktual_output_kontribusi'] = $aktualKontribusiByInstansi[$id_instansi];
                if (isset($aktualKontribusiByInstansi[$id_instansi])) {
                    // rumus nilai aktual output mandiri
                    $instansi['nilai_aktual_output_mandiri'] = $aktualKontribusiByInstansi[$id_instansi] / $targetKontribusiByInstansi[$id_instansi];
                }
            }

            if(isset($targetAksiKontribusiByInstansi[$id_instansi])) {
                $instansi['nilai_target_aksi_kontribusi'] = $targetAksiKontribusiByInstansi[$id_instansi];
            }

            if(isset($aktualAksiKontribusiByInstansi[$id_instansi])) {
                $instansi['nilai_aktual_aksi_kontribusi'] = $aktualAksiKontribusiByInstansi[$id_instansi];
                $instansi['nilai_aktual_aksi_mandiri'] = $aktualAksiKontribusiByInstansi[$id_instansi] / $targetAksiKontribusiByInstansi[$id_instansi];
            }

        }

        $this->calculateScoreBasedOnChildren($dashboardOutput);

        // start ambil semua instansi yang ada pada aksi
        // cari paren dari output
        $parentDariOutput = $dashboardStore->findById($dashboardOutput['id_parent']);
        $outputYes = $dashboardStore->findBy(['_id', 'IN', $parentDariOutput['id_children']]);

        $allInstansiInAksi = [];
        foreach($outputYes as $output) {
           if(isset($output['nilai_kld'])) {
                foreach($output['nilai_kld'] as $nilaiKld) {
                    if(isset($nilaiKld['id_instansi']) && isset($nilaiKld['nama'])) {
                        $id_instansi = $nilaiKld['id_instansi'];
                        $nama = $nilaiKld['nama'];
                        $targetAksi = null;
                        $aktualAksi = null;
                        // jika di dalam output kld memiliki nilai target aksi dan aktual aksi
                        if(isset($nilaiKld['nilai_target_aksi_kontribusi'])) {
                            $targetAksi = $nilaiKld['nilai_target_aksi_kontribusi'];
                        }
                        if(isset($nilaiKld['nilai_aktual_aksi_kontribusi'])) {
                            $aktualAksi = $nilaiKld['nilai_aktual_aksi_kontribusi'];
                        }
                        $allInstansiInAksi[] = [
                            'id_instansi' => $id_instansi,
                            'nama' => $nama,
                            'nilai_target_aksi_kontribusi' => $targetAksi,
                            'nilai_aktual_aksi_kontribusi' => $aktualAksi,
                        ];
                    }
                }
           }
        }
        $getInstansiAndScore = [];
        foreach($allInstansiInAksi as $ins) {
            $id_instansi = $ins['id_instansi'];
            if(!isset($getInstansiAndScore[$id_instansi])) {
                $getInstansiAndScore[$id_instansi] = $ins;
            } else {
                $getInstansiAndScore[$id_instansi]['nilai_target_aksi_kontribusi'] += $ins['nilai_target_aksi_kontribusi'];
                $getInstansiAndScore[$id_instansi]['nilai_aktual_aksi_kontribusi'] += $ins['nilai_aktual_aksi_kontribusi'];
            }
        }
        // end ambil instansi pada aksi

        $dashboardAksi = $dashboardStore->findById($dashboardOutput['id_parent']);
        $dashboardAksi["nilai_target_aksi_kontribusi"] = 1 / 15 * $jumlahNilaiTargetOutput  * 100;
        if($dashboardAksi["nilai_target_aksi_kontribusi"] != 0) {
            $dashboardAksi["nilai_target_aksi_mandiri"] = $dashboardAksi["nilai_target_aksi_kontribusi"] / $dashboardAksi["nilai_target_aksi_kontribusi"];
        }
        $dashboardAksi["nilai_aktual_aksi_kontribusi"] = 1 / 15 * $jumlahNilaiAktualOutput;
        if($dashboardAksi["nilai_aktual_aksi_kontribusi"] != 0) {
            $dashboardAksi["nilai_aktual_aksi_mandiri"] = $dashboardAksi["nilai_aktual_aksi_kontribusi"] / $dashboardAksi["nilai_target_aksi_kontribusi"];
        }

        // nilai kld level aksi
        $nilaiAksiInstansi = [
            'nilai_target_aksi_kontribusi' => null,
            'nilai_aktual_aksi_kontribusi' => null,
            'nilai_target_aksi_mandiri' => 1,
            'nilai_aktual_aksi_mandiri' => null,
        ];        

        $nilaiKldAksi = [];
        foreach($getInstansiAndScore as $key => $instansi) {
            $nilaiDanInstansiMerge = array_merge($instansi, $nilaiAksiInstansi);
            if (isset($getInstansiAndScore[$key]['nilai_target_aksi_kontribusi'])) {
                $nilaiDanInstansiMerge['nilai_target_aksi_kontribusi'] = $getInstansiAndScore[$key]['nilai_target_aksi_kontribusi'];
            }
            if (isset($getInstansiAndScore[$key]['nilai_aktual_aksi_kontribusi'])) {
                $nilaiDanInstansiMerge['nilai_aktual_aksi_kontribusi'] = $getInstansiAndScore[$key]['nilai_aktual_aksi_kontribusi'];
            }
            if ($nilaiDanInstansiMerge['nilai_target_aksi_kontribusi'] != 0) {
                $nilaiDanInstansiMerge['nilai_aktual_aksi_mandiri'] = $nilaiDanInstansiMerge['nilai_aktual_aksi_kontribusi'] / $nilaiDanInstansiMerge['nilai_target_aksi_kontribusi'];
            }
            // $nilaiDanInstansiMerge['nilai_aktual_aksi_mandiri'] = $nilaiDanInstansiMerge['nilai_aktual_aksi_kontribusi'] / $nilaiDanInstansiMerge['nilai_target_aksi_kontribusi'];
            $nilaiKldAksi[] = $nilaiDanInstansiMerge;
        }
        $dashboardAksi['nilai_kld'] = $nilaiKldAksi;

        $this->calculateScoreBasedOnChildren($dashboardAksi);

        $dashboardStranasPK = $dashboardStore->findById($dashboardAksi['id_parent']);
        $this->calculateScoreBasedOnChildren($dashboardStranasPK);
    }

    private function calculateScoreBasedOnChildren($parent) {
        // menghitung nilai score dashboard berdasarkan score anak2nya

        $id_children_list = $parent['id_children'];

        // dapatkan children's scores, jumlahkan
        $score = 0;
        $dashboardStore = new SleekDbStore('dashboard');
        foreach($id_children_list as $id_child) {
            $child = $dashboardStore->findById(intval($id_child));
            if (!is_null($child)) {
                $score += floatval($child['score']) * floatval($child['weight']) / 100;
            }
        }
        $parent['score'] = $score;
        $dashboardStore->update($parent);
    }

    public function logframeList($id_periode, $level) {
        $store = new SleekDbStore('logframe');
        $logframes = $store->findBy([
            ['id_periode', '=', intval($id_periode)],
            ['level', '=', intval($level)]
        ],
        [
            "_id" => "asc", "id_logframe" => "asc" // urut bds _id dan id_logframe
        ]
    );
        $dataLogframes = [];
        foreach($logframes as $logframe) {
            if(array_key_exists('id_parent', $logframe)) {
                $logframe['parent'] = $this->findParentLogframe($logframe['id_parent']);
            }
            $dataLogframes[] = $logframe;
        }
        return $dataLogframes;
    }
    
    public function detailLogframe() {
        $this->checkIsAuthenticated();
        $getParams = $this->request->get();
        $params = $this->validate(
            $getParams,
            [
                'id_periode' => ['validators' => ['required', 'digit']],
                'id_logframe' => ['validators' => ['required']],
            ]
        );
        $id_periode = $params['id_periode'];
        $id_logframe = $params['id_logframe'] ?? null;

        $store = new SleekDbStore('logframe');
        $logframes = $store->findBy([
            ['id_periode', '=', intval($id_periode)],
            ['id_logframe', '=', $id_logframe]
        ]);
        $dataLogframes = [];
        foreach($logframes as $logframe) {
            if(array_key_exists('id_parent', $logframe)) {
                $logframe['parent'] = $this->findParentLogframe($logframe['id_parent']);
            }
            $dataLogframes[] = $logframe;
        }
        return $dataLogframes;
    }
    
}