#include "video_decoder.h"

#include <android/log.h>
#include <media/NdkMediaCodec.h>
#include <media/NdkMediaExtractor.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <cstring>
#include <cerrno>

#define LOG_TAG "VideoDecoder"
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)

static video_decoder::DecoderState g_decoder = {};

static int memfd_create_compat(const char* name, unsigned int flags) {
#ifdef __NR_memfd_create
    return (int)syscall(__NR_memfd_create, name, flags);
#else
    (void)flags;
    char path[256];
    snprintf(path, sizeof(path), "/data/local/tmp/camera_magic_shmem_%s_%d", name, getpid());
    int fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0666);
    if (fd >= 0) unlink(path);
    return fd;
#endif
}

static bool init_shmem(uint32_t width, uint32_t height) {
    size_t size = video_decoder::calc_shmem_size(width, height);
    
    int fd = memfd_create_compat("cam2magic", 0);
    if (fd < 0) {
        ALOGE("memfd_create failed: %s", strerror(errno));
        return false;
    }
    
    if (ftruncate(fd, size) < 0) {
        ALOGE("ftruncate failed: %s", strerror(errno));
        close(fd);
        return false;
    }
    
    uint8_t* base = (uint8_t*)mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (base == MAP_FAILED) {
        ALOGE("mmap failed: %s", strerror(errno));
        close(fd);
        return false;
    }
    
    memset(base, 0, size);
    
    auto* header = reinterpret_cast<video_decoder::RingBufferHeader*>(base);
    header->magic = video_decoder::RING_MAGIC;
    header->version = video_decoder::RING_VERSION;
    header->frame_count = video_decoder::RING_FRAME_COUNT;
    header->frame_width = width;
    header->frame_height = height;
    header->frame_size = (uint32_t)(width * height * 3 / 2);
    header->write_index = 0;
    header->read_index = 0;
    header->sequence = 0;
    header->flags = 0;
    header->last_timestamp = 0;
    
    g_decoder.shmem_fd = fd;
    g_decoder.shmem_base = base;
    g_decoder.shmem_size = size;
    g_decoder.header = header;
    
    ALOGI("Shared memory initialized: fd=%d, size=%zu, %ux%u", fd, size, width, height);
    return true;
}

static void release_shmem() {
    if (g_decoder.shmem_base && g_decoder.shmem_base != MAP_FAILED) {
        munmap(g_decoder.shmem_base, g_decoder.shmem_size);
        g_decoder.shmem_base = nullptr;
    }
    if (g_decoder.shmem_fd >= 0) {
        close(g_decoder.shmem_fd);
        g_decoder.shmem_fd = -1;
    }
    g_decoder.header = nullptr;
    g_decoder.shmem_size = 0;
}

bool video_decoder::write_frame_to_ring(const uint8_t* y_data, const uint8_t* u_data,
                                         const uint8_t* v_data, int y_stride, int uv_stride,
                                         int width, int height) {
    if (!g_decoder.header || !g_decoder.shmem_base) return false;
    
    uint32_t frame_size = g_decoder.header->frame_size;
    uint32_t frame_count = g_decoder.header->frame_count;
    uint32_t idx = g_decoder.header->write_index % frame_count;
    uint8_t* dst = get_frame_ptr(g_decoder.shmem_base, idx, frame_size, frame_count);
    
    int y_plane_size = width * height;
    int uv_plane_size = (width / 2) * (height / 2);
    
    uint8_t* dst_y = dst;
    uint8_t* dst_u = dst + y_plane_size;
    uint8_t* dst_v = dst + y_plane_size + uv_plane_size;
    
    for (int row = 0; row < height; row++) {
        memcpy(dst_y + row * width, y_data + row * y_stride, width);
    }
    for (int row = 0; row < height / 2; row++) {
        memcpy(dst_u + row * (width / 2), u_data + row * uv_stride, width / 2);
        memcpy(dst_v + row * (width / 2), v_data + row * uv_stride, width / 2);
    }
    
    g_decoder.header->write_index = (g_decoder.header->write_index + 1) % (frame_count * 2);
    g_decoder.header->sequence++;
    
    return true;
}

static void* decoder_thread_func(void* arg) {
    (void)arg;
    ALOGI("Decoder thread started");
    
    g_decoder.header->flags |= video_decoder::FLAG_DECODER_RUNNING;
    
    AMediaExtractor* extractor = AMediaExtractor_new();
    if (!extractor) {
        ALOGE("Failed to create MediaExtractor");
        g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
        return nullptr;
    }
    
    int fd = open(g_decoder.video_path.c_str(), O_RDONLY);
    if (fd < 0) {
        ALOGE("Failed to open video file: %s (%s)", g_decoder.video_path.c_str(), strerror(errno));
        AMediaExtractor_delete(extractor);
        g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
        return nullptr;
    }
    
    media_status_t status = AMediaExtractor_setDataSourceFd(extractor, fd, 0, 0);
    if (status != AMEDIA_OK) {
        ALOGE("AMediaExtractor_setDataSourceFd failed: %d", status);
        close(fd);
        AMediaExtractor_delete(extractor);
        g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
        return nullptr;
    }
    close(fd);
    
    int video_track = -1;
    int num_tracks = AMediaExtractor_getTrackCount(extractor);
    for (int i = 0; i < num_tracks; i++) {
        AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, i);
        if (!format) continue;
        
        const char* mime = nullptr;
        if (AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime) && mime) {
            if (strncmp(mime, "video/", 6) == 0) {
                video_track = i;
                
                int32_t w = 0, h = 0;
                AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &w);
                AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &h);
                ALOGI("Video track %d: %s, %dx%d", i, mime, w, h);
                
                if (w > 0 && h > 0) {
                    g_decoder.width = w;
                    g_decoder.height = h;
                }
                AMediaFormat_delete(format);
                break;
            }
        }
        AMediaFormat_delete(format);
    }
    
    if (video_track < 0) {
        ALOGE("No video track found");
        AMediaExtractor_delete(extractor);
        g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
        return nullptr;
    }
    
    AMediaExtractor_selectTrack(extractor, video_track);
    AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, video_track);
    
    const char* mime = nullptr;
    AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime);
    if (!mime) {
        ALOGE("Cannot get MIME type from format");
        AMediaFormat_delete(format);
        AMediaExtractor_delete(extractor);
        g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
        return nullptr;
    }
    
    AMediaCodec* codec = AMediaCodec_createDecoderByType(mime);
    if (!codec) {
        ALOGE("Failed to create MediaCodec decoder");
        AMediaFormat_delete(format);
        AMediaExtractor_delete(extractor);
        g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
        return nullptr;
    }
    
    status = AMediaCodec_configure(codec, format, nullptr, nullptr, 0);
    if (status != AMEDIA_OK) {
        ALOGE("AMediaCodec_configure failed: %d", status);
        AMediaCodec_delete(codec);
        AMediaFormat_delete(format);
        AMediaExtractor_delete(extractor);
        g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
        return nullptr;
    }
    AMediaFormat_delete(format);
    
    status = AMediaCodec_start(codec);
    if (status != AMEDIA_OK) {
        ALOGE("AMediaCodec_start failed: %d", status);
        AMediaCodec_delete(codec);
        AMediaExtractor_delete(extractor);
        g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
        return nullptr;
    }
    
    ALOGI("Decoder started, feeding frames");
    
    bool input_done = false;
    bool output_done = false;
    int frame_count = 0;
    const int MAX_FRAMES_PER_LOOP = 100000;
    
    while (g_decoder.running && !output_done && frame_count < MAX_FRAMES_PER_LOOP) {
        if (!input_done) {
            ssize_t buf_idx = AMediaCodec_dequeueInputBuffer(codec, 2000);
            if (buf_idx >= 0) {
                size_t buf_size = 0;
                uint8_t* buf = AMediaCodec_getInputBuffer(codec, buf_idx, &buf_size);
                if (buf) {
                    ssize_t sample_size = AMediaExtractor_readSampleData(extractor, buf, buf_size);
                    if (sample_size < 0) {
                        ALOGI("Input EOS reached");
                        AMediaCodec_queueInputBuffer(codec, buf_idx, 0, 0, 0,
                                                     AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
                        input_done = true;
                    } else {
                        int64_t pts = AMediaExtractor_getSampleTime(extractor);
                        AMediaCodec_queueInputBuffer(codec, buf_idx, 0, sample_size, pts, 0);
                        AMediaExtractor_advance(extractor);
                    }
                }
            }
        }
        
        AMediaCodecBufferInfo info;
        ssize_t out_idx = AMediaCodec_dequeueOutputBuffer(codec, &info, 2000);
        if (out_idx >= 0) {
            if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
                ALOGI("Output EOS reached, frame_count=%d", frame_count);
                output_done = true;
            }
            
            size_t out_size = 0;
            uint8_t* out_buf = AMediaCodec_getOutputBuffer(codec, out_idx, &out_size);
            if (out_buf && info.size > 0) {
                int w = g_decoder.width;
                int h = g_decoder.height;
                
                if (w > 0 && h > 0 && out_size >= (size_t)(w * h * 3 / 2)) {
                    const uint8_t* y = out_buf;
                    const uint8_t* u = out_buf + w * h;
                    const uint8_t* v = u + (w / 2) * (h / 2);
                    
                    video_decoder::write_frame_to_ring(y, u, v, w, w / 2, w, h);
                    frame_count++;
                    
                    if (frame_count % 300 == 0) {
                        ALOGI("Decoded %d frames", frame_count);
                    }
                }
            }
            
            AMediaCodec_releaseOutputBuffer(codec, out_idx, false);
        } else if (out_idx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
            AMediaFormat* new_fmt = AMediaCodec_getOutputFormat(codec);
            int32_t w = 0, h = 0;
            AMediaFormat_getInt32(new_fmt, AMEDIAFORMAT_KEY_WIDTH, &w);
            AMediaFormat_getInt32(new_fmt, AMEDIAFORMAT_KEY_HEIGHT, &h);
            ALOGI("Output format changed: %dx%d", w, h);
            if (w > 0 && h > 0) {
                g_decoder.width = w;
                g_decoder.height = h;
            }
            AMediaFormat_delete(new_fmt);
        }
    }
    
    if (g_decoder.loop && !output_done) {
        ALOGI("Looping video (frame_count=%d)", frame_count);
    }
    
    AMediaCodec_stop(codec);
    AMediaCodec_delete(codec);
    AMediaExtractor_delete(extractor);
    
    g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
    ALOGI("Decoder thread finished, total frames: %d", frame_count);
    return nullptr;
}

int video_decoder::init_decoder(const char* video_path, bool loop) {
    if (!video_path) return -1;
    
    g_decoder.video_path = video_path;
    g_decoder.loop = loop;
    g_decoder.running = false;
    g_decoder.width = 0;
    g_decoder.height = 0;
    g_decoder.rotation = 0;
    g_decoder.shmem_fd = -1;
    g_decoder.shmem_base = nullptr;
    g_decoder.shmem_size = 0;
    g_decoder.header = nullptr;
    
    ALOGI("Decoder initialized for: %s (loop=%d)", video_path, loop);
    return 0;
}

int video_decoder::start_decoder() {
    if (g_decoder.running) {
        ALOGW("Decoder already running");
        return 0;
    }
    
    if (g_decoder.width <= 0 || g_decoder.height <= 0) {
        g_decoder.width = 1920;
        g_decoder.height = 1080;
        ALOGW("No video dimensions, defaulting to 1920x1080");
    }
    
    if (!init_shmem((uint32_t)g_decoder.width, (uint32_t)g_decoder.height)) {
        return -1;
    }
    
    g_decoder.running = true;
    
    int ret = pthread_create(&g_decoder.decoder_thread, nullptr, decoder_thread_func, nullptr);
    if (ret != 0) {
        ALOGE("pthread_create failed: %s", strerror(ret));
        g_decoder.running = false;
        release_shmem();
        return -1;
    }
    
    ALOGI("Decoder thread started");
    return 0;
}

void video_decoder::stop_decoder() {
    if (!g_decoder.running) return;
    
    ALOGI("Stopping decoder");
    g_decoder.running = false;
    
    if (g_decoder.decoder_thread) {
        pthread_join(g_decoder.decoder_thread, nullptr);
        g_decoder.decoder_thread = 0;
    }
    
    release_shmem();
    ALOGI("Decoder stopped");
}

void video_decoder::release_decoder() {
    stop_decoder();
    g_decoder.video_path.clear();
    ALOGI("Decoder released");
}

int video_decoder::get_shmem_fd() {
    return g_decoder.shmem_fd;
}

int video_decoder::get_decoder_width() {
    return g_decoder.width;
}

int video_decoder::get_decoder_height() {
    return g_decoder.height;
}

bool video_decoder::is_decoder_running() {
    return g_decoder.running && (g_decoder.header && 
           (g_decoder.header->flags & FLAG_DECODER_RUNNING));
}
