//
// Created by 阳坤 on 2022/3/1.
//

#include <json/reader.h>
#include <json/writer.h>
#include <sys/stat.h>
#include <signal.h>
#include <execinfo.h>
#include "video_similarity.h"


const int MAX_STACK_FRAMES = 128;

void sig_crash(int sig) {
    printf("crash---->sig=%d \n", sig);
    FILE *fd;
    struct stat buf;
    const char *crash_path = "./video_similarity_comparison_crash.txt";
    stat(crash_path, &buf);
    if (buf.st_size > 10 * 1000 * 1000) { // 超过10兆则清空内容
        fd = fopen(crash_path, "w");
    } else {
        fd = fopen(crash_path, "at");
    }

    if (NULL == fd) {
        exit(0);
    }
    try {
        char szLine[512] = {0,};
        time_t t = time(NULL);
        tm *now = localtime(&t);
        int nLen1 = sprintf(szLine,
                            "#########################################################\n[%04d-%02d-%02d %02d:%02d:%02d][crash signal number:%d]\n",
                            now->tm_year + 1900,
                            now->tm_mon + 1,
                            now->tm_mday,
                            now->tm_hour,
                            now->tm_min,
                            now->tm_sec,
                            sig);
        printf("crash---->%s \n", szLine);
        fwrite(szLine, 1, strlen(szLine), fd);
#ifdef __linux
        void* array[MAX_STACK_FRAMES];
        size_t size = 0;
        char** strings = NULL;
        size_t i, j;
        signal(sig, SIG_DFL);
        size = backtrace(array, MAX_STACK_FRAMES);
        strings = (char**)backtrace_symbols(array, size);
        //fprintf(stderr, "oncrash;\n");
        for (i = 0; i < size; ++i)
        {
            char szLine[512] = {0, };
            sprintf(szLine, "%d %s\n", i, strings[i]);
            fwrite(szLine, 1, strlen(szLine), fd);

            std::string symbol(strings[i]);
            size_t pos1 = symbol.find_first_of("[");
            size_t pos2 = symbol.find_last_of("]");
            std::string address = symbol.substr(pos1 + 1, pos2 - pos1 -1);
            char cmd[128] = {0, };
            sprintf(cmd, "addr2line -e /home/libpiaoquan_java_opencv.so %s", address.c_str()); // test为应用程序名称，需要改为用户自己的应用程序名
            FILE *fPipe = popen(cmd, "r");
            if(fPipe != NULL){
                char buff[1024];
                memset(buff, 0, sizeof(buff));
                char* ret = fgets(buff, sizeof(buff), fPipe);
                pclose(fPipe);
                fwrite(ret, 1, strlen(ret), fd);
            }
        }
       free(strings);
#endif // __linux
    } catch (...) {
        //
    }
    signal(sig, SIG_DFL);
    fflush(fd);
    fclose(fd);
    fd = NULL;
}

long video_similarity_detection_init(const char *url, VideoSimilarity **vs) {
    auto *ctx = (VideoSimilarityContext *) malloc(sizeof(VideoSimilarityContext));
    if (!ctx)return -1;
    memset(ctx, 0, sizeof(VideoSimilarityContext));
    if (url) {
        *vs = json2VideoSimilarity(url);
        if (!(*vs))return -1L;
        if ((*vs)->videoPath)
            ctx->video_path = (*vs)->videoPath;
        else return -1L;
    }
    return (long) ctx;
}

static const char *GetFileName(const char *ptr, int n) {
    int i = n; //这里i只是为循环即使终止了也未找到/而准备
    ptr += n; //把指针移到字符串的尾部，即'\0'处
    while (i-- > 0) {
        if ((*ptr--) == '/') //指针不断回移并判断是否为/符号
        {
            break; //从后向前遇到第一个/后退出循环
        }
    }
    ptr += 2;
    return ptr; //反回最后一个/后面的字符串即名称
}

int video_similarity_detection_start(long id, int force_keyframe,
                                     DisableMediaType disableMediaType,
                                     std::vector<VideoSimilarityModel *> *lists) {
    if (id <= 0)return -1;
    auto *ctx = (VideoSimilarityContext *) id;
    long d_id = initDecoder(ctx->video_path, force_keyframe, disableMediaType);

    ctx->decode_obj_id = d_id;
    if (ctx->decode_obj_id <= 0)return ctx->decode_obj_id;
    int exit = 0, ret = 0;
    av_read_decode_frame(ctx->decode_obj_id);
    auto *dctx = (DecoderContext *) ctx->decode_obj_id;
    dctx->vs_decodes = *lists;
    int index = 0;
    auto endTimeMs = (*lists)[index]->endTimeMs;
    while (!exit) {
        AVFrame *video_frame = NULL;
        dctx->video_queue->PopFront(video_frame);
        if (video_frame == NULL) {
            LOGE("read video frame end.");
            break;
        }

        int len = 0;
        if (ctx->log) fprintf(ctx->log, "fingerprintFromFFAVFrame 1\n");
        char image_hash[64] = {0};
        len = fingerprintFromFFAVFrame(ctx->log, video_frame, image_hash);
        if (ctx->log) fprintf(ctx->log, "fingerprintFromFFAVFrame exit \n");
        printf("fingerprintFromFFAVFrame exit \n");
        if (len > 64) {
            LOGE("size=%d \n", len);
        }
        if (len > 0) {
            auto *model = (ImageHashModel *) malloc(sizeof(ImageHashModel));
            memset(model, 0, sizeof(ImageHashModel));
            model->img_len = len;
            model->image_hash = static_cast<char *>(malloc(sizeof(char) * len + 1));
            memset((void *) model->image_hash, '\0', sizeof(char) * len + 1);
            string append_array;
            for (int i = 0; i < len; ++i) {
                char c = image_hash[i];
                if (c == 0) {
                    append_array.append("0");
                } else if (c == 1) {
                    append_array.append("1");
                }
            }
            append_array.append("\0");
            memcpy((char *) model->image_hash, append_array.c_str(), strlen(append_array.c_str()));
            if (video_frame->pts >= endTimeMs && (*lists).size() > 1 && index + 1 < (*lists).size()) {
                index++;
                endTimeMs = (*lists)[index]->endTimeMs;
            }
            model->pts = video_frame->pts;
            (*lists)[index]->hashs.push_back(model);
        }
        if (ctx->log)fprintf(ctx->log, "image_hash  \n");
        if (ctx->log) fprintf(ctx->log, "av_frame_free\n");
        av_frame_free(&video_frame);
        if (ctx->log) fprintf(ctx->log, "av_frame_free 2\n");
    }

    if (ctx->log) fprintf(ctx->log, "loop exit \n");
    LOGE("loop exit \n");
    return 0;
}

int video_similarity_detection_close(long id) {
    if (id <= 0)return -1;
    auto *ctx = (VideoSimilarityContext *) id;
    LOGE("video_similarity_detection_close \n");
    if (ctx->video_path) {
//        free((void *) ctx->video_path);
        ctx->video_path = NULL;
    }
    if (ctx->log) {
        fclose(ctx->log);
        ctx->log = NULL;
    }
    close_decoder(ctx->decode_obj_id);
    free(ctx);
    ctx = NULL;
    LOGE("video_similarity_detection_close out\n");
    return 0;
}

VideoSimilarity *json2VideoSimilarity(const char *json) {
    VideoSimilarity *videoSimilarity = NULL;
    Json::Reader reader;
    Json::Value value;
    if (reader.parse(json, value)) {
        videoSimilarity = new VideoSimilarity();
        Json::Value json_obj = value;
        if (json_obj.isMember("videoPath"))
            videoSimilarity->videoPath = strdup(json_obj["videoPath"].asString().c_str());
        if (json_obj.isMember("clips") && json_obj["clips"].type() == Json::arrayValue) {
            for (int i = 0; i < json_obj["clips"].size(); ++i) {
                auto *videoSim = (VideoSimilarityModel *) malloc(sizeof(VideoSimilarityModel));
                memset(videoSim, 0, sizeof(VideoSimilarityModel));
                videoSim->startTimeMs = json_obj["clips"][i]["startTimeMs"].asInt();
                videoSim->endTimeMs = json_obj["clips"][i]["endTimeMs"].asInt();
                videoSimilarity->clips.push_back(videoSim);
            }
        }
    }
    return videoSimilarity;
}

const char *videoSimilarity2json(VideoSimilarity *videoSimilarity) {
    if (!videoSimilarity)return NULL;
    Json::Value obj;
    Json::Value array;
    Json::StreamWriterBuilder builder;
    if (videoSimilarity->videoPath) {
        obj["videoPath"] = videoSimilarity->videoPath;
        free((void *) videoSimilarity->videoPath);
        videoSimilarity->videoPath = NULL;
    }

    for (auto it = videoSimilarity->clips.begin();
         it != videoSimilarity->clips.end();) {
        auto item = *it;
        Json::Value v;
        Json::Value v_array;
        v["startTimeMs"] = item->startTimeMs;
        v["endTimeMs"] = item->endTimeMs;

        for (auto it2 = item->hashs.begin();
             it2 != item->hashs.end();) {
            ImageHashModel *item2 = *it2;
            Json::Value va;
            if (item->hashs.size() <= 1499) {
//                LOGE("size=%d %d\n", item->hashs.size(), item2->img_len);
            }
            if (item2->image_hash && item2->img_len > 0) {
//                string append_array;
//                for (int i = 0; i < item2->img_len; ++i) {
//                    char c = item2->image_hash[i];
//                    if (c == 0) {
//                        append_array.append("0");
//                    } else if (c == 1) {
//                        append_array.append("1");
//                    }
//                }
                va["imageHash"] = item2->image_hash;
//                printf("item2->image_hash address=%p \n",&item2->image_hash);
                free((void *) item2->image_hash);
                item2->image_hash = NULL;
            }
            va["hashSize"] = item2->img_len;
            va["pts"] = item2->pts;
            v_array.append(va);
            free(item2);
            item2 = NULL;
            it2 = item->hashs.erase(it2);
        }
        v["hashs"] = v_array;
        array.append(v);

        free(item);
        item = NULL;
        it = videoSimilarity->clips.erase(it);
    }

    obj["clips"] = array;
    const std::string json_file = Json::writeString(builder, obj);
    const char *ret_json = strdup(json_file.c_str());

    videoSimilarity->clips.clear();
    return ret_json;
}

/**
 * 获取视频相似值列表
 * @param inputfile
 * @return
 */
const char *get_video_similarity_list(const char *inputjson) {
    signal(SIGABRT, sig_crash);
    signal(SIGSEGV, sig_crash);
    if (!inputjson)return NULL;
    printf("get_video_similarity_list=%s \n", inputjson);
    VideoSimilarity *vs = NULL;
    long id = video_similarity_detection_init(inputjson, &vs);
    const char *ret_json = NULL;
    if (id > 0) {
        if (!(((VideoSimilarityContext *) id)->log)) {
            int n = strlen(((VideoSimilarityContext *) id)->video_path); //计算字符串的长度（包括'\0')
            const char *name = GetFileName(((VideoSimilarityContext *) id)->video_path, n); //把字符串及其长度传给函数
            printf("%s\n", name); //name就是那个名称，可以输出
            char args[512];
//            sprintf(args, "/Users/devyk/Data/Project/sample/github_code/OpenCVSample/temp/%s.log", name);
            sprintf(args, "/datalog/ffmpeg_opencv/%s.log", name);
            ((VideoSimilarityContext *) id)->log = fopen(args, "wb+");
            printf("args===%s \n", args);
            if (((VideoSimilarityContext *) id)->log)fprintf(((VideoSimilarityContext *) id)->log, inputjson);
        }
        if (((VideoSimilarityContext *) id)->log)
            fprintf(((VideoSimilarityContext *) id)->log, "video_similarity_detection_start \n");
        video_similarity_detection_start(id, 0, AUDIO,
                                         &vs->clips);
        if (((VideoSimilarityContext *) id)->log)
            fprintf(((VideoSimilarityContext *) id)->log, "video_similarity_detection_start exit \n");
        video_similarity_detection_close(id);
        ret_json = videoSimilarity2json(vs);
        delete vs;
        vs = NULL;
        LOGE("get_video_similarity_list out \n");
    }
    return ret_json;
}
