/**
 * @file   shared_mem_frame.cpp
 * @author Martin Piatka    <piatka@cesnet.cz>
 */
/*
 * Copyright (c) 2018 CESNET, z. s. p. o.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, is permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of CESNET nor the names of its contributors may be
 *    used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "shared_mem_frame.hpp"

#include <cmath>

#ifndef GUI_BUILD
#include "config.h"
#include "config_unix.h"
#include "config_win32.h"
#include "video_codec.h"
#include "video_frame.h"
#include "debug.h"

#else

#include <stdio.h>
#define error_msg(...) fprintf(stderr, __VA_ARGS__)
#endif // GUI_BUILD

Shared_mem::Shared_mem(const char *key) :
        mem_size(4096),
        locked(false),
        owner(false)
{
        setKey(key);
}

Shared_mem::Shared_mem() : Shared_mem("ultragrid_preview") {  }

Shared_mem::~Shared_mem(){
        destroy();
}

void Shared_mem::setKey(const char *key){
        shared_mem.setKey(key);
}

bool Shared_mem::create(){
        if(shared_mem.create(mem_size) == false){
                /* Creating shared memory could fail because of shared memory
                 * left over after crash. Here we try to release such memory.*/
                shared_mem.attach();
                shared_mem.detach();
                if(shared_mem.create(mem_size) == false){
                        error_msg("[Shared mem] Can't create shared memory!\n");
                        return false;
                }
        }

        Shared_mem_frame *frame = get_frame_and_lock();
        if(!frame)
                return false;
        frame->width = 20;
        frame->height = 20;
        frame->should_detach = false;
        unlock();
        owner = true;
        return true;
}

bool Shared_mem::attach(){
        return shared_mem.attach();
}

bool Shared_mem::detach(){
        if(locked)
                unlock();

        owner = false;
        return shared_mem.detach();
}

void Shared_mem::destroy(){
        if(!owner)
                return;

        Shared_mem_frame *sframe = get_frame_and_lock();
        if(sframe){
                sframe->should_detach = true;
        }
        detach();
}

bool Shared_mem::is_attached(){
        return shared_mem.isAttached();
}

bool Shared_mem::lock(){
        /* Locking should fail when the shared memory is not attached,
         * but for some reason QT sometimes succeeds in locking unattached
         * memory (bug in QT maybe?)
         */
        if(!is_attached())
                return false;

        if(shared_mem.lock()){
                locked = true;
                return true;
        }
        return false;
}

bool Shared_mem::unlock(){
        if(shared_mem.unlock()){
                locked = false;
                return true;
        }
        return false;
}

Shared_mem_frame *Shared_mem::get_frame_and_lock(){
#ifndef GUI_BUILD
        if(reconfiguring){
                if(shared_mem.attach()){
                        // Shared mem is still not detached by the GUI
                        shared_mem.detach();
                        return nullptr;
                } else {
                        if(shared_mem.create(mem_size) == false){
                                error_msg("[Shared mem] Can't create shared memory (get_frame)!\n");
                                return nullptr;
                        } else {
                                owner = true;
                        }
                        lock();
                        struct Shared_mem_frame *sframe = (Shared_mem_frame*) shared_mem.data();
                        sframe->width = scaledW_pad;
                        sframe->height = scaledH;
                        sframe->should_detach = false;
                        reconfiguring = false;
                }
        } else {
                if(!is_attached()) attach();
                lock();
        }
#else
        if(!is_attached()) attach();
        lock();
#endif // GUI_BUILD

        struct Shared_mem_frame *sframe = (Shared_mem_frame*) shared_mem.data();
        if(sframe && sframe->should_detach){
                detach();
                return nullptr;
        }

        return sframe;
}

#ifndef GUI_BUILD

void Shared_mem::check_reconf(struct video_desc in_desc){
        if (video_desc_eq(desc, in_desc))
                return;

        desc = in_desc;

        /* We need to destroy the shared memory segment
         * and recreate it with a new size. To destroy it all processes
         * must detach it. We detach here and then wait until the GUI detaches */
        destroy();

        const float target_width = 960;
        const float target_height = 540;

        float scale = ((in_desc.width / target_width) + (in_desc.height / target_height)) / 2.f;
        if(scale < 1)
                scale = 1;
        scale = std::round(scale);
        scaleF = (int) scale;

        scaledW = in_desc.width / scaleF;
        //OpenGL wants the width to be divisable by 4
        scaledW_pad = ((scaledW + 4 - 1) / 4) * 4;
        scaledH = in_desc.height / scaleF;
        scaled_frame.resize(get_bpp(in_desc.color_spec) * scaledW * scaledH);
        mem_size = get_bpp(preview_codec) * scaledW_pad * scaledH + sizeof(Shared_mem_frame);

        reconfiguring = true;
}

void Shared_mem::put_frame(struct video_frame *frame){
        // Only he instance that created the shared frame can write to it
        if(!owner){
                return;
        }

        decoder_t dec = get_decoder_from_to(frame->color_spec, preview_codec, true);
        if (!dec) {
                LOG(LOG_LEVEL_WARNING) << "[Shared mem] Cannot find decoder from " <<
                        get_codec_name(frame->color_spec) << " to " <<
                        get_codec_name(preview_codec) << ".\n";
                return;
        }

        check_reconf(video_desc_from_frame(frame));

        int src_line_len = vc_get_linesize(desc.width, frame->color_spec);
        int block_size = get_pf_block_size(frame->color_spec);
        assert(block_size > 0);
        int dst = 0;
        for(unsigned y = 0; y < desc.height; y += scaleF){
                for(int x = 0; x + scaleF * block_size <= src_line_len; x += scaleF * block_size){
                        memcpy(scaled_frame.data() + dst, frame->tiles[0].data + y*src_line_len + x, block_size);
                        dst += block_size;
                }
        }

        struct Shared_mem_frame *sframe = get_frame_and_lock();

        if(!sframe)
                return;

        int dst_line_len_pad = vc_get_linesize(scaledW_pad, preview_codec);
        int dst_line_len = vc_get_linesize(scaledW, preview_codec);
        src_line_len = vc_get_linesize(scaledW, frame->color_spec);
        for(int i = 0; i < scaledH; i++){
                dec(sframe->pixels + dst_line_len_pad * i,
                                scaled_frame.data() + src_line_len * i,
                                dst_line_len,
                                0, 8, 16);
        }

        unlock();
}

#endif // GUI_BUILD
