/*
 * image_processor.h - 3a image processor
 *
 *  Copyright (c) 2014-2015 Intel Corporation
 *
 * 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.
 *
 * Author: Wind Yuan <feng.yuan@intel.com>
 */

#include "image_processor.h"
#include "xcam_thread.h"

namespace XCam {

void
ImageProcessCallback::process_buffer_done (ImageProcessor *processor, const SmartPtr<VideoBuffer> &buf) {
    XCAM_UNUSED (processor);
    XCAM_ASSERT (buf.ptr() && processor);

    int64_t ts = buf->get_timestamp();
    XCAM_UNUSED (ts);
    XCAM_LOG_DEBUG (
        "processor(%s) handled buffer(" XCAM_TIMESTAMP_FORMAT ") successfully",
        XCAM_STR(processor->get_name()),
        XCAM_TIMESTAMP_ARGS (ts));
}

void
ImageProcessCallback::process_buffer_failed (ImageProcessor *processor, const SmartPtr<VideoBuffer> &buf)
{
    XCAM_ASSERT (buf.ptr() && processor);

    int64_t ts = buf->get_timestamp();
    XCAM_UNUSED (ts);
    XCAM_LOG_WARNING (
        "processor(%s) handled buffer(" XCAM_TIMESTAMP_FORMAT ") failed",
        XCAM_STR(processor->get_name()),
        XCAM_TIMESTAMP_ARGS (ts));
}

void
ImageProcessCallback::process_image_result_done (ImageProcessor *processor, const SmartPtr<X3aResult> &result)
{
    XCAM_UNUSED (processor);
    XCAM_ASSERT (result.ptr() && processor);

    int64_t ts = result->get_timestamp();
    XCAM_UNUSED (ts);

    XCAM_LOG_DEBUG (
        "processor(%s) processed result(type:%d, timestamp:" XCAM_TIMESTAMP_FORMAT ") done",
        XCAM_STR(processor->get_name()),
        (int)result->get_type(),
        XCAM_TIMESTAMP_ARGS (ts));
}

class ImageProcessorThread
    : public Thread
{
public:
    ImageProcessorThread (ImageProcessor *processor)
        : Thread ("image_processor")
        , _processor (processor)
    {}
    ~ImageProcessorThread () {}

    virtual bool loop ();

private:
    ImageProcessor *_processor;
};

bool ImageProcessorThread::loop ()
{
    XCamReturn ret = _processor->buffer_process_loop ();
    if (ret == XCAM_RETURN_NO_ERROR || ret == XCAM_RETURN_ERROR_TIMEOUT)
        return true;
    return false;
}

class X3aResultsProcessThread
    : public Thread
{
    typedef SafeList<X3aResult> ResultQueue;
public:
    X3aResultsProcessThread (ImageProcessor *processor)
        : Thread ("x3a_results_process_thread")
        , _processor (processor)
    {}
    ~X3aResultsProcessThread () {}

    XCamReturn push_result (SmartPtr<X3aResult> &result) {
        _queue.push (result);
        return XCAM_RETURN_NO_ERROR;
    }

    void triger_stop () {
        _queue.pause_pop ();
    }

    virtual bool loop ();

private:
    ImageProcessor  *_processor;
    ResultQueue      _queue;
};

bool X3aResultsProcessThread::loop ()
{
    X3aResultList result_list;
    SmartPtr<X3aResult> result;

    result = _queue.pop (-1);
    if (!result.ptr ())
        return false;

    result_list.push_back (result);
    while ((result = _queue.pop (0)).ptr ()) {
        result_list.push_back (result);
    }

    XCamReturn ret = _processor->process_3a_results (result_list);
    if (ret != XCAM_RETURN_NO_ERROR) {
        XCAM_LOG_DEBUG ("processing 3a result failed");
    }

    return true;
}
ImageProcessor::ImageProcessor (const char* name)
    : _name (NULL)
    , _callback (NULL)
{
    if (name)
        _name = strndup (name, XCAM_MAX_STR_SIZE);

    SmartPtr<ImageProcessorThread> processor_thread = new ImageProcessorThread (this);
    XCAM_ASSERT (processor_thread.ptr ());
    _processor_thread = processor_thread;

    SmartPtr<X3aResultsProcessThread> results_thread = new X3aResultsProcessThread (this);
    XCAM_ASSERT (results_thread.ptr ());
    _results_thread = results_thread;
}

ImageProcessor::~ImageProcessor ()
{
    if (_name)
        xcam_free (_name);
}

bool
ImageProcessor::set_callback (ImageProcessCallback *callback)
{
    XCAM_ASSERT (!_callback);
    _callback = callback;
    return true;
}

XCamReturn
ImageProcessor::start()
{
    XCamReturn ret = XCAM_RETURN_NO_ERROR;
    if (!_results_thread->start ()) {
        return XCAM_RETURN_ERROR_THREAD;
    }
    if (!_processor_thread->start ()) {
        return XCAM_RETURN_ERROR_THREAD;
    }
    ret = emit_start ();
    if (ret != XCAM_RETURN_NO_ERROR) {
        XCAM_LOG_WARNING ("ImageProcessor(%s) emit start failed", XCAM_STR (_name));
        _video_buf_queue.pause_pop ();
        _results_thread->triger_stop ();
        _processor_thread->stop ();
        _results_thread->stop ();
        return ret;
    }
    XCAM_LOG_INFO ("ImageProcessor(%s) started", XCAM_STR (_name));
    return XCAM_RETURN_NO_ERROR;
}

XCamReturn
ImageProcessor::stop()
{
    _video_buf_queue.pause_pop ();
    _results_thread->triger_stop ();

    emit_stop ();

    _processor_thread->stop ();
    _results_thread->stop ();
    XCAM_LOG_DEBUG ("ImageProcessor(%s) stopped", XCAM_STR (_name));
    return XCAM_RETURN_NO_ERROR;
}

XCamReturn
ImageProcessor::push_buffer (SmartPtr<VideoBuffer> &buf)
{
    if (_video_buf_queue.push (buf))
        return XCAM_RETURN_NO_ERROR;

    XCAM_LOG_DEBUG ("processor push buffer failed");
    return XCAM_RETURN_ERROR_UNKNOWN;
}

XCamReturn
ImageProcessor::push_3a_results (X3aResultList &results)
{
    XCAM_ASSERT (!results.empty ());
    XCamReturn ret = XCAM_RETURN_NO_ERROR;
    for (X3aResultList::iterator i_res = results.begin();
            i_res != results.end(); ++i_res) {
        SmartPtr<X3aResult> &res = *i_res;

        ret = _results_thread->push_result (res);
        if (ret != XCAM_RETURN_NO_ERROR)
            break;
    }

    XCAM_FAIL_RETURN(
        WARNING,
        ret == XCAM_RETURN_NO_ERROR,
        ret,
        "processor(%s) push 3a results failed", XCAM_STR(get_name()));
    return XCAM_RETURN_NO_ERROR;
}

XCamReturn
ImageProcessor::push_3a_result (SmartPtr<X3aResult> &result)
{
    XCamReturn ret = _results_thread->push_result (result);
    XCAM_FAIL_RETURN(
        WARNING,
        ret == XCAM_RETURN_NO_ERROR,
        ret,
        "processor(%s) push 3a result failed", XCAM_STR(get_name()));
    return XCAM_RETURN_NO_ERROR;
}

XCamReturn
ImageProcessor::process_3a_results (X3aResultList &results)
{
    X3aResultList valid_results;
    XCamReturn ret = XCAM_RETURN_NO_ERROR;

    filter_valid_results (results, valid_results);
    if (valid_results.empty())
        return XCAM_RETURN_BYPASS;

    ret = apply_3a_results (valid_results);

    if (ret != XCAM_RETURN_NO_ERROR && ret != XCAM_RETURN_BYPASS) {
        XCAM_LOG_WARNING ("processor(%s) apply results failed", XCAM_STR(get_name()));
        return ret;
    }

    if (_callback) {
        for (X3aResultList::iterator i_res = valid_results.begin();
                i_res != valid_results.end(); ++i_res) {
            SmartPtr<X3aResult> &res = *i_res;
            _callback->process_image_result_done (this, res);
        }
    }

    return ret;
}

XCamReturn
ImageProcessor::process_3a_result (SmartPtr<X3aResult> &result)
{
    X3aResultList valid_results;
    XCamReturn ret = XCAM_RETURN_NO_ERROR;

    if (!can_process_result(result))
        return XCAM_RETURN_BYPASS;

    ret = apply_3a_result (result);

    if (ret != XCAM_RETURN_NO_ERROR && ret != XCAM_RETURN_BYPASS) {
        XCAM_LOG_WARNING ("processor(%s) apply result failed", XCAM_STR(get_name()));
        return ret;
    }

    if (_callback) {
        _callback->process_image_result_done (this, result);
    }

    return ret;
}

void
ImageProcessor::filter_valid_results (X3aResultList &input, X3aResultList &valid_results)
{
    for (X3aResultList::iterator i_res = input.begin(); i_res != input.end(); ) {
        SmartPtr<X3aResult> &res = *i_res;
        if (can_process_result(res)) {
            valid_results.push_back (res);
            input.erase (i_res++);
        } else
            ++i_res;
    }
}

void
ImageProcessor::notify_process_buffer_done (const SmartPtr<VideoBuffer> &buf)
{
    if (_callback)
        _callback->process_buffer_done (this, buf);
}

void
ImageProcessor::notify_process_buffer_failed (const SmartPtr<VideoBuffer> &buf)
{
    if (_callback)
        _callback->process_buffer_failed (this, buf);
}

XCamReturn
ImageProcessor::buffer_process_loop ()
{
    XCamReturn ret = XCAM_RETURN_NO_ERROR;
    SmartPtr<VideoBuffer> new_buf;
    SmartPtr<VideoBuffer> buf = _video_buf_queue.pop();

    if (!buf.ptr())
        return XCAM_RETURN_ERROR_MEM;

    ret = this->process_buffer (buf, new_buf);
    if (ret < XCAM_RETURN_NO_ERROR) {
        XCAM_LOG_DEBUG ("processing buffer failed");
        notify_process_buffer_failed (buf);
        return ret;
    }

    if (new_buf.ptr ())
        notify_process_buffer_done (new_buf);

    return XCAM_RETURN_NO_ERROR;
}

XCamReturn
ImageProcessor::emit_start ()
{
    return XCAM_RETURN_NO_ERROR;
}

void
ImageProcessor::emit_stop ()
{
}

};
