/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

/*
 * Copyright (c) 2021, Institute of Computing Technology
 * Author: wanglei21c@mails.ucas.ac.cn
 */

#include "odla_dump.h"

#include "device/device.h"
#include "graph/tensor.h"
#include "graph/node.h"
#include "graph/graph.h"
#include "graph/subgraph.h"
#include "operator/op.h"
#include "utility/log.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef _MSC_VER
#include <windows.h>
#else
#include <sys/stat.h>
#include <sys/time.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef _MSC_VER
#include <windows.h>
#else
#include <sys/stat.h>
#include <sys/time.h>
#endif

void odla_data_dump(const char* filename, int8_t* data, int w, int h, int c)
{
#ifdef OPENDLA_LOG_
    FILE* fp;

    fp = fopen(filename, "w");

    unsigned int line_stride = w * 8;
    unsigned int surface_stride = line_stride * h;

    fprintf(fp, "blobs {\n");
    for (int i = 0; i < c; i++)
    {
        for (int j = 0; j < h; j++)
        {
            for (int k = 0; k < w; k++)
            {
                int surface_index = i / 8;
                fprintf(fp, "  double_data: %d\n", data[surface_stride * surface_index + line_stride * j + k + i % 8]);
                fprintf(stdout, "address: %lx data: %d\n", ((uint64_t)data) + surface_stride * surface_index + line_stride * j + k + i % 8, data[surface_stride * surface_index + line_stride * j + k + i % 8]);
            }
        }
    }
    fprintf(fp, "  shape {\n");
    fprintf(fp, "    dim: 1\n");
    fprintf(fp, "    dim: %d\n", c);
    fprintf(fp, "    dim: %d\n", h);
    fprintf(fp, "    dim: %d\n", w);
    fprintf(fp, "  }\n");
    fprintf(fp, "}\n");

    fclose(fp);
#endif
}

int print_tensor_data_value_odla(FILE* file, const struct tensor* tensor, int offset)
{
    switch (tensor->data_type)
    {
    case TENGINE_DT_FP32:
    {
        float* base_ptr = (float*)tensor->data;
        float val = base_ptr[offset];
        if (val < 0)
            fprintf(file, "%.4f ", val);
        else
            fprintf(file, " %.4f ", val);
        break;
    }
        //        case TENGINE_DT_FP16:
        //        {
        //            fp16_t* base_ptr = (fp16_t*)tensor->data;
        //            fp16_t val = base_ptr[offset];
        //
        //            float val_fp32 = fp16_to_fp32(val);
        //
        //            if (val_fp32 < 0)
        //                fprintf(file, "%.4f ", val_fp32);
        //            else
        //                fprintf(file, " %.4f ", val_fp32);
        //            break;
        //        }
    case TENGINE_DT_UINT8:
    {
        uint8_t* base_ptr = (uint8_t*)tensor->data;
        uint8_t val = base_ptr[offset];

        float scale = tensor->scale;
        int32_t zero_point = tensor->zero_point;

        float val_fp32 = (float)((int)val - (int)zero_point) * scale;
        if (val_fp32 < 0)
            fprintf(file, "%.4f ", val_fp32);
        else
            fprintf(file, " %.4f ", val_fp32);
        break;
    }
    case TENGINE_DT_INT8:
    {
        int8_t* base_ptr = (int8_t*)tensor->data;
        int8_t val = base_ptr[offset];
        fprintf(file, "%d ", val);

        break;
    }
    case TENGINE_DT_INT16:
    {
        int16_t* base_ptr = (int16_t*)tensor->data;
        int16_t val = base_ptr[offset];
        fprintf(file, "%d ", val);

        break;
    }
    case TENGINE_DT_INT32:
    {
        int32_t* base_ptr = (int32_t*)tensor->data;
        int8_t val = base_ptr[offset];

        float scale = tensor->scale;
        float val_fp32 = (float)val * scale;

        if (val_fp32 < 0)
            fprintf(file, "%.6f ", val_fp32);
        else
            fprintf(file, " %.6f ", val_fp32);
    }
    }

    return 0;
}

const char* get_tensor_data_type_string_odla(int data_type)
{
    switch (data_type)
    {
    case TENGINE_DT_FP32:
        return "fp32";
    case TENGINE_DT_FP16:
        return "fp16";
    case TENGINE_DT_INT8:
        return "int8";
    case TENGINE_DT_UINT8:
        return "uint8";
    case TENGINE_DT_INT32:
        return "int32";
    case TENGINE_DT_INT16:
        return "int16";
    default:
        return "unknown";
    }
}

void print_tensor_data_to_file_odla(FILE* file, const struct tensor* tensor)
{
    switch (tensor->dim_num)
    {
    case 5:
    {
        int dim5 = tensor->dims[0], batch = tensor->dims[1], channel = 0, height = 0, width = 0;

        if (TENGINE_LAYOUT_NCHW == tensor->layout)
        {
            channel = tensor->dims[2];
            height = tensor->dims[3];
            width = tensor->dims[4];
        }
        if (TENGINE_LAYOUT_NHWC == tensor->layout)
        {
            height = tensor->dims[2];
            width = tensor->dims[3];
            channel = tensor->dims[4];
        }

        if (TENGINE_DT_FP32 == tensor->data_type)
        {
            fprintf(file, "Shape is {%d %d %d %d %d}, data type is fp32\n", dim5, batch, channel, height, width);
        }
        else
        {
            if (TENGINE_DT_FP16 == tensor->data_type)
            {
                fprintf(file, "Shape is {%d %d %d %d %d}, data type is fp16, cast to fp32\n", dim5, batch, channel, height, width);
            }
            else
            {
                const char* type_name = get_tensor_data_type_string_odla(tensor->data_type);
                fprintf(file, "Shape is {%d %d %d %d %d}, data type is %s, inverse quantization to fp32\n", dim5, batch, channel, height, width, type_name);
            }
        }

        for (int d5 = 0; d5 < dim5; d5++)
        {
            fprintf(file, "Dim5 %d:\n", d5);

            for (int n = 0; n < batch; n++)
            {
                fprintf(file, "\tBatch %d:\n", n);

                for (int ch = 0; ch < channel; ch++)
                {
                    fprintf(file, "\t\tChannel %d:\n", ch);

                    for (int h = 0; h < height; h++)
                    {
                        fprintf(file, "\t\t\t");

                        for (int w = 0; w < width; w++)
                        {
                            int offset = 0;

                            if (TENGINE_LAYOUT_NCHW == tensor->layout)
                            {
                                offset += d5 * batch * channel * height * width;
                                offset += n * channel * height * width;
                                offset += ch * height * width;
                                offset += h * width;
                                offset += w;
                            }
                            if (TENGINE_LAYOUT_NHWC == tensor->layout)
                            {
                                offset += d5 * batch * channel * height * width;
                                offset += n * channel * height * width;
                                offset += ch;
                                offset += h * width * channel;
                                offset += w * channel;
                            }

                            print_tensor_data_value_odla(file, tensor, offset);
                        }
                        fprintf(file, "\n");
                    }
                    fprintf(file, "\n");
                }
                fprintf(file, "\n");
            }
            fprintf(file, "\n");
        }

        break;
    }
    case 4:
    {
        int batch = tensor->dims[0], channel = 0, height = 0, width = 0;

        if (TENGINE_LAYOUT_NCHW == tensor->layout)
        {
            channel = tensor->dims[1];
            height = tensor->dims[2];
            width = tensor->dims[3];
        }
        if (TENGINE_LAYOUT_NHWC == tensor->layout)
        {
            height = tensor->dims[1];
            width = tensor->dims[2];
            channel = tensor->dims[3];
        }

        if (TENGINE_DT_FP32 == tensor->data_type)
        {
            fprintf(file, "Shape is {%d %d %d %d}, data type is fp32\n", batch, channel, height, width);
        }
        else
        {
            if (TENGINE_DT_FP16 == tensor->data_type)
            {
                fprintf(file, "Shape is {%d %d %d %d}, data type is fp16, cast to fp32\n", batch, channel, height, width);
            }
            else
            {
                const char* type_name = get_tensor_data_type_string_odla(tensor->data_type);
                fprintf(file, "Shape is {%d %d %d %d}, data type is %s, inverse quantization to fp32\n", batch, channel, height, width, type_name);
            }
        }

        for (int n = 0; n < batch; n++)
        {
            fprintf(file, "Batch %d:\n", n);

            for (int ch = 0; ch < channel; ch++)
            {
                fprintf(file, "\tChannel %d:\n", ch);

                for (int h = 0; h < height; h++)
                {
                    fprintf(file, "\t\t");

                    for (int w = 0; w < width; w++)
                    {
                        int offset = 0;

                        if (TENGINE_LAYOUT_NCHW == tensor->layout)
                        {
                            offset += n * channel * height * width;
                            offset += ch * height * width;
                            offset += h * width;
                            offset += w;
                        }
                        if (TENGINE_LAYOUT_NHWC == tensor->layout)
                        {
                            offset += n * channel * height * width;
                            offset += ch;
                            offset += h * width * channel;
                            offset += w * channel;
                        }

                        print_tensor_data_value_odla(file, tensor, offset);
                    }
                    fprintf(file, "\n");
                }
                fprintf(file, "\n");
            }
            fprintf(file, "\n");
        }

        break;
    }
    case 3:
    {
        int batch = 0, height = 0, width = 0;

        if (TENGINE_LAYOUT_NCHW == tensor->layout)
        {
            batch = tensor->dims[0];
            height = tensor->dims[1];
            width = tensor->dims[2];
        }
        if (TENGINE_LAYOUT_NHWC == tensor->layout)
        {
            height = tensor->dims[0];
            width = tensor->dims[1];
            batch = tensor->dims[2];
        }

        if (TENGINE_DT_FP32 == tensor->data_type)
        {
            fprintf(file, "Shape is {%d %d %d}, data type is fp32\n", batch, height, width);
        }
        else
        {
            if (TENGINE_DT_FP16 == tensor->data_type)
            {
                fprintf(file, "Shape is {%d %d %d}, data type is fp16, cast to fp32\n", batch, height, width);
            }
            else
            {
                const char* type_name = get_tensor_data_type_string_odla(tensor->data_type);
                fprintf(file, "Shape is {%d %d %d}, data type is %s, inverse quantization to fp32\n", batch, height, width, type_name);
            }
        }

        for (int n = 0; n < batch; n++)
        {
            for (int h = 0; h < height; h++)
            {
                fprintf(file, "Channel %d:\n", h);
                fprintf(file, "\t");

                for (int w = 0; w < width; w++)
                {
                    int offset = 0;

                    if (TENGINE_LAYOUT_NCHW == tensor->layout)
                    {
                        offset += n * height * width;
                        offset += h * width;
                        offset += w;
                    }
                    if (TENGINE_LAYOUT_NHWC == tensor->layout)
                    {
                        offset += h;
                        offset += n * width * height;
                        offset += w * height;
                    }

                    print_tensor_data_value_odla(file, tensor, offset);
                }
                fprintf(file, "\n");
            }
            fprintf(file, "\n");
        }

        break;
    }
    case 2:
    {
        int batch = 0, width = 0;

        if (TENGINE_LAYOUT_NCHW == tensor->layout)
        {
            batch = tensor->dims[0];
            width = tensor->dims[1];
        }
        if (TENGINE_LAYOUT_NHWC == tensor->layout)
        {
            batch = tensor->dims[0];
            width = tensor->dims[1];
        }

        if (TENGINE_DT_FP32 == tensor->data_type)
        {
            fprintf(file, "Shape is {%d %d}, data type is fp32\n", batch, width);
        }
        else
        {
            if (TENGINE_DT_FP16 == tensor->data_type)
            {
                fprintf(file, "Shape is {%d %d}, data type is fp16, cast to fp32\n", batch, width);
            }
            else
            {
                const char* type_name = get_tensor_data_type_string_odla(tensor->data_type);
                fprintf(file, "Shape is {%d %d}, data type is %s, inverse quantization to fp32\n", batch, width, type_name);
            }
        }

        for (int n = 0; n < batch; n++)
        {
            for (int w = 0; w < width; w++)
            {
                int offset = 0;

                offset += n * width;
                offset += w;

                print_tensor_data_value_odla(file, tensor, offset);
            }
            fprintf(file, "\n");
        }

        break;
    }
    case 1:
    {
        int width = tensor->dims[0];

        fprintf(file, "Shape is {%d}, data type is fp32\n", width);

        for (int w = 0; w < width; w++)
        {
            print_tensor_data_value_odla(file, tensor, w);
        }

        break;
    }
    default:
        printf("Input dimension %d not to be supported.\n", tensor->dim_num);
    }
}

char* replace_string_character_odla(char* src_str, char* dst_str, char* target_char, char* replaced_char)
{
    char* p;
    char* _out = dst_str;
    char* _str = src_str;
    char* _src = target_char;
    char* _dst = replaced_char;
    size_t src_size = strlen(_src);
    size_t dst_size = strlen(_dst);
    size_t len = 0;

    do
    {
        p = strstr(_str, _src);
        if (p == 0)
        {
            strcpy(_out, _str);
            return dst_str;
        }
        len = p - _str;
        memcpy(_out, _str, len);
        memcpy(_out + len, _dst, dst_size);
        _str = p + src_size;
        _out = _out + len + dst_size;
    } while (p);

    return dst_str;
}

void extract_feature_from_tensor_odla(const char* comment, const char* layer_name, const struct tensor* tensor)
{
    // 1. deal with saving path
    char save_dir[256] = {'0'};

    const char* env_path = getenv(TENGINE_DUMP_DIR);

    if (NULL != env_path && '\0' != env_path[0] && (256 - 2) > strlen(env_path))
    {
        strcpy(save_dir, env_path);

        if ('/' == save_dir[strlen(env_path) - 1] || '\\' == save_dir[strlen(env_path) - 1])
        {
#ifdef _MSC_VER
            save_dir[strlen(env_path)] = '\\';
            save_dir[strlen(env_path) + 1] = 0;
#else
            save_dir[strlen(env_path)] = '/';
            save_dir[strlen(env_path) + 1] = 0;
#endif
        }
    }
    else
    {
        //        TLOG_WARNING("Tengine: Env var \"TENGINE_DUMP_DIR\" is too long(%d vs. 254). Using default path.\n", strlen(env_path));
        sprintf(save_dir, "./output/");
#ifdef _MSC_VER
        CreateDirectoryA(save_dir, NULL);
#else
        int ret = mkdir(save_dir, S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH);
//        if (0 != ret)
//        {
//            TLOG_WARNING("Tengine: Create saving folder failed(%d), skip dump.\n", ret);
//            return;
//        }
#endif
    }

    // 2. deal with layer name
    char layer_short_name[64], layer_legal_name[64];

    if (64 < strlen(layer_name))
    {
        memcpy(layer_short_name, layer_name, 64 - 1);
        layer_short_name[64 - 1] = 0;
    }
    else
    {
        strcpy(layer_short_name, layer_name);
    }

    replace_string_character_odla(layer_short_name, layer_legal_name, "/", "-");

    // 3. join path
    char output_file_path[512] = {'0'};

    if (strlen(layer_legal_name) + strlen(save_dir) + strlen(comment) > 256 - 16)
    {
        TLOG_WARNING("Tengine: Name of saving file is too long(%d vs. %d), skip dump.\n", strlen(layer_legal_name) + strlen(save_dir) + strlen(comment), 256 - 16);
        return;
    }

    sprintf(output_file_path, "%s%s_%s_blob_data.txt", save_dir, layer_legal_name, comment);

    FILE* file = fopen(output_file_path, "w");
    if (NULL == file)
    {
        fprintf(stderr, "Tengine: Open file(%s) failed, skip dump\n", output_file_path);
        return;
    }

    print_tensor_data_to_file_odla(file, tensor);

    // close file
    fclose(file);
    file = NULL;
}

void dump_sub_graph_odla(struct subgraph* sub_graph)
{
    TLOG_INFO("Sub graph[%d]: {%8s } has %d nodes, %d input tensors, %d output tensors.\n", sub_graph->index, sub_graph->device->name, sub_graph->node_num, sub_graph->input_num, sub_graph->output_num);
    TLOG_INFO("\tSub nodes: [ ");

    for (int j = 0; j < sub_graph->node_num - 1; j++)
    {
        int node_id = sub_graph->node_list[j];
        TLOG_INFO("%d, ", node_id);
    }
    TLOG_INFO("%d ].\n", sub_graph->node_list[sub_graph->node_num - 1]);

    TLOG_INFO("\tSub input tensors: [ ");
    for (int j = 0; j < sub_graph->input_num - 1; j++)
    {
        int tensor_id = sub_graph->input_tensor_list[j];
        TLOG_INFO("%d, ", tensor_id);
    }
    TLOG_INFO("%d ].\n", sub_graph->input_tensor_list[sub_graph->input_num - 1]);

    TLOG_INFO("\tSub output tensors: [ ");
    for (int j = 0; j < sub_graph->output_num - 1; j++)
    {
        int tensor_id = sub_graph->output_tensor_list[j];
        TLOG_INFO("%d, ", tensor_id);
    }
    TLOG_INFO("%d ].\n", sub_graph->output_tensor_list[sub_graph->output_num - 1]);
}