/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <sstream>
#include <queue>
#include <vector>
#include <map>
#include <string>
#include <unistd.h>
#include <sys/time.h>

namespace {
struct DumpEntity {
    const std::string windowName;
    const std::string displayId;
    const std::string pid;
    const std::string windId;
};
struct FpsInfo {
    int fps;
    int preFps;
    std::vector<long long> jitters;
    std::queue<long long> timeStampQ;
    long long lastFrameReadyTime;
    long long currentFpsTime;
    FpsInfo()
    {
        fps = 0;
        preFps = 0;
        lastFrameReadyTime = 0;
        currentFpsTime = 0;
    }
};
struct FpsConfig {
    const long long mod = 1e9;
    long long lastReadyTime;
    int fpsGb;
    bool jump;
    bool refresh;
    int cnt;
    int zeroNum;
    FpsConfig()
    {
        lastReadyTime = -1;
        fpsGb = 0;
        jump = false;
        refresh = false;
        cnt = 0;
        zeroNum = 0;
    }
};
}
static void StrSplit(const std::string &content, const std::string &sp, std::vector<std::string> &out)
{
    size_t index = 0;
    while (index != std::string::npos) {
        size_t tEnd = content.find_first_of(sp, index);
        std::string tmp = content.substr(index, tEnd - index);
        if (tmp != "" && tmp != " ") {
            out.push_back(tmp);
        }
        if (tEnd == std::string::npos) {
            break;
        }
        index = tEnd + 1;
    }
}

static std::string GetLayer()
{
    std::vector<DumpEntity> dumpEntityList;
    std::string curFocusId = "-1";
    const std::string cmd = "hidumper -s WindowManagerService -a -a";
    std::string cmdExc = cmd;
    FILE *fd = popen(cmdExc.c_str(), "r");
    if (fd != nullptr) {
        int lineNum = 0;
        std::string line;
        char buf[1024] = {'\0'};
        const int paramFifteen = 15;
        const int paramThree = 3;
        const int windowNameIndex = 0;
        const int windowIdIndex = 3;
        const int focusNameIndex = 2;
        while ((fgets(buf, sizeof(buf), fd)) != nullptr) {
            line = buf;
            if (line[0] == '-' || line[0] == ' ') {
                continue;
            }
            std::vector<std::string> params;
            StrSplit(line, " ", params);
            if (params[windowNameIndex].find("WindowName")!= std::string::npos &&
                params[windowIdIndex].find("WinId")!= std::string::npos) {
                continue;
            }
            if (params.size() == paramFifteen) {
                DumpEntity dumpEntity { params[0], params[1], params[2], params[3] };
                dumpEntityList.push_back(dumpEntity);
            }
            if (params.size() == paramThree) {
                curFocusId = params[focusNameIndex];
                break;
            }
            lineNum++;
        }
        pclose(fd);
    }
    std::string resultWindowName = "NA";
    int curId = std::stoi(curFocusId);
    for (size_t i = 0; i < dumpEntityList.size(); i++) {
        DumpEntity dumpItem = dumpEntityList[i];
        int curWinId = std::stoi(dumpItem.windId);
        if (curId == curWinId) {
            resultWindowName = dumpItem.windowName;
        }
    }
    return resultWindowName;
}
static void ProcessResult(FILE *fp, FpsConfig &fpsConfig, FpsInfo &fpsInfo) 
{
    char tmp[1024];
    while (fgets(tmp, sizeof(tmp), fp) != nullptr) {
        long long frameReadyTime = 0;
        std::stringstream sstream;
        sstream << tmp;
        sstream >> frameReadyTime;
        fpsConfig.cnt++;
        if (frameReadyTime == 0) {
            fpsConfig.zeroNum++;
            continue;
        }
        if (fpsConfig.lastReadyTime >= frameReadyTime) {
            fpsConfig.lastReadyTime = -1;
            continue;
        }
        fpsConfig.refresh = true;
        long long tFrameReadyTime = frameReadyTime / fpsConfig.mod;
        long long tLastReadyTime = fpsConfig.lastReadyTime / fpsConfig.mod;
        long long lastFrame = -1;
        if (tFrameReadyTime == tLastReadyTime) {
            (fpsInfo.timeStampQ).push(frameReadyTime);
        } else if (tFrameReadyTime == tLastReadyTime + 1) {
            fpsConfig.jump = true;
            lastFrame = fpsInfo.lastFrameReadyTime;
            fpsConfig.lastReadyTime = frameReadyTime;
            int fpsTmp = 0;
            fpsInfo.jitters.clear();
            while (!(fpsInfo.timeStampQ).empty()) {
                fpsTmp++;
                long long currFrame = (fpsInfo.timeStampQ.front());
                if (lastFrame != -1) {
                    long long jitter = currFrame - lastFrame;
                    fpsInfo.jitters.push_back(jitter);
                }
                lastFrame = currFrame;
                (fpsInfo.timeStampQ).pop();
            }
            fpsConfig.fpsGb = fpsTmp;
            (fpsInfo.timeStampQ).push(frameReadyTime);
            fpsInfo.lastFrameReadyTime = lastFrame;
        } else if (tFrameReadyTime > tLastReadyTime + 1) {
            fpsConfig.jump = true;
            fpsConfig.lastReadyTime = frameReadyTime;
            while (!(fpsInfo.timeStampQ).empty()) {
                (fpsInfo.timeStampQ).pop();
            }
            (fpsInfo.timeStampQ).push(frameReadyTime);
        }
    }
}

static FpsInfo GetSurfaceFrame(std::string name, FpsConfig &fpsConfig)
{
    static std::map<std::string, FpsInfo> fpsMap;
    if (fpsMap.count(name) == 0) {
        FpsInfo tmp;
        tmp.fps = 0;
        tmp.preFps = 0;
        fpsMap[name] = tmp;
    }
    FpsInfo &fpsInfo = fpsMap[name];
    fpsInfo.fps = 0;
    struct timeval tv;
    gettimeofday(&tv, nullptr);
    fpsInfo.currentFpsTime = tv.tv_sec * 1e3 + tv.tv_usec / 1e3;
    std::string cmd = "hidumper -s 10 -a \"fps " + name + "\"";
    std::string cmdExc = cmd;
    FILE *fp = popen(cmdExc.c_str(), "r");
    if (fp == nullptr) {
        return fpsInfo;
    }
    static long long lastLineTime;
    if (!(fpsInfo.timeStampQ).empty()) {
        fpsConfig.lastReadyTime = (fpsInfo.timeStampQ).back();
        lastLineTime = (fpsInfo.timeStampQ).back();
    }
    ProcessResult(fp, fpsConfig, fpsInfo);
    pclose(fp);
    const int maxZeroNum = 120;
    const int minPrintLine = 5;
    if (fpsConfig.zeroNum >= maxZeroNum) {
        while (!(fpsInfo.timeStampQ.empty())) {
            fpsInfo.timeStampQ.pop();
        }
        fpsInfo.fps = 0;
        return fpsInfo;
    }

    if (fpsConfig.cnt < minPrintLine) {
        fpsInfo.fps = fpsInfo.preFps;
        return fpsInfo;
    }
    
    if (!fpsInfo.timeStampQ.empty() && fpsInfo.timeStampQ.back() == lastLineTime) {
        fpsInfo.fps = 0;
        return fpsInfo;
    }

    if (fpsConfig.fpsGb > 0) {
        fpsInfo.fps = fpsConfig.fpsGb;
        fpsInfo.preFps = fpsConfig.fpsGb;
        return fpsInfo;
    } else if (fpsConfig.refresh && !fpsConfig.jump) {
        fpsInfo.fps = fpsInfo.preFps;
        return fpsInfo;
    } else {
        fpsInfo.fps = 0;
        return fpsInfo;
    }
}

static void ReplaceString(std::string &res)
{
    std::string flagOne = "\r";
    std::string flagTwo = "\n";
    std::string::size_type ret = res.find(flagOne);
    while (ret != res.npos) {
        res.replace(ret, 1, "");
        ret = res.find(flagOne);
    }
    ret = res.find(flagTwo);
    while (ret != res.npos) {
        res.replace(ret, 1, "");
        ret = res.find(flagTwo);
    }
}

static bool LoadCmd(const std::string &cmd, std::string &result)
{
    std::string cmdExc = cmd;
    FILE *fd = popen(cmdExc.c_str(), "r");
    if (fd == nullptr) {
        return false;
    }
    char buf[1024] = {'\0'};
    int ret = fread(buf, sizeof(buf), 1, fd);
    if (ret >= 0) {
        result = buf;
    }
    if (pclose(fd) == -1) {
        std::cout << "" << std::endl;
    }
    ReplaceString(result);
    return ret >= 0 ? true : false;
}
static std::string GetSurFace()
{
    std::string cmdResult;
    std::string cmdString1 = "hidumper -s 10 -a sur";
    std::string cmdString2 = "face | grep sur";
    std::string cmdString3 = "face";
    LoadCmd(cmdString1 + cmdString2 + cmdString3, cmdResult);
    size_t position1 = cmdResult.find("[");
    size_t position2 = cmdResult.find("]");
    return cmdResult.substr(position1 + 1, position2 - position1 - 1);
}
int main(int argc, char *argv[])
{
    if (argc < 2) {
        printf("exec failed, require one param | example: GP_daemon_fps 10");
        return 0;
    }
    int num = 1;
    if (!strcmp(argv[1], "")) {
        printf("the args of num must be not-null!\n");
    } else {
        num = atoi(argv[1]);
        if (num < 0) {
            printf("set num:%d not valid arg\n", num);
        }
        printf("set num:%d success\n", num);
        FpsInfo gfpsInfo;
        FpsInfo gfpsUniteInfo;
        std::string layerName;
        std::string tempLayerName;
        struct timeval start;
        struct timeval end;
        std::string uniteLayer = "DisplayNode";
        uniteLayer = GetSurFace();
        unsigned long oneSec = 1000000;
        std::string cmdResult;
        for (int i = 0; i < num; i++) {
            unsigned long runTime;
            gettimeofday(&start, nullptr);
            tempLayerName = GetLayer();
            if (i == 0) {
                layerName = tempLayerName;
                LoadCmd("hidumper -s 10 -a \"fpsClear DisplayNode\"", cmdResult);
                LoadCmd("hidumper -s 10 -a \"fpsClear" + layerName + "\"", cmdResult);
            } else {
                if (layerName.compare(tempLayerName) != 0) {
                    layerName = tempLayerName;
                    LoadCmd("hidumper -s 10 -a \"fpsClear" + layerName + "\"", cmdResult);
                }
            }
            FpsConfig fpsConfig;
            FpsConfig fpsUniteConfig;
            gfpsInfo = GetSurfaceFrame(layerName, fpsConfig);
            gfpsUniteInfo = GetSurfaceFrame(uniteLayer, fpsUniteConfig);
            if (gfpsUniteInfo.fps > gfpsInfo.fps)
            {
                printf("fps:%d|%lld\n", gfpsUniteInfo.fps, gfpsUniteInfo.currentFpsTime);
            } else {
                printf("fps:%d|%lld\n", gfpsInfo.fps, gfpsInfo.currentFpsTime);
            }
            fflush(stdout);
            gettimeofday(&end, nullptr);
            runTime = end.tv_sec * 1e6 - start.tv_sec * 1e6 + end.tv_usec - start.tv_usec;
            if (runTime < oneSec) {
                usleep(oneSec - runTime);
            }
        }
    }
    printf("GP_daemon_fps exec finished!\n");
    return 0;
}