/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * 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.
 */

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>

#include <utils/StrongPointer.h>

#include "Log.h"
#include "audio/Buffer.h"
#include "StringUtil.h"
#include "SimpleScriptExec.h"
#include "SignalProcessingImpl.h"
#include "task/TaskCase.h"

enum ToPythonCommandType {
    EHeader             = 0x0,
    ETerminate          = 0x1,
    EFunctionName       = 0x2,
    EAudioMono          = 0x4,
    EAudioStereo        = 0x5,
    EValue64Int         = 0x8,
    EValueDouble        = 0x9,
    EExecutionResult    = 0x10
};

const android::String8 \
    SignalProcessingImpl::MAIN_PROCESSING_SCRIPT("test_description/processing_main.py");

SignalProcessingImpl::SignalProcessingImpl()
    : mChildRunning(false),
      mBuffer(1024)
{

}

SignalProcessingImpl::~SignalProcessingImpl()
{
    if (mSocket.get() != NULL) {
        int terminationCommand [] = {ETerminate, 0};
        send((char*)terminationCommand, sizeof(terminationCommand));
        mSocket->release();
    }
    if (mChildRunning) {
        waitpid(mChildPid, NULL, 0);
    }
}

#define CHILD_LOGE(x...) do { fprintf(stderr, x); \
    fprintf(stderr, " %s - %d\n", __FILE__, __LINE__); } while(0)

const int CHILD_WAIT_TIME_US = 100000;

bool SignalProcessingImpl::init(const android::String8& script)
{
    pid_t pid;
    if ((pid = fork()) < 0) {
        LOGE("SignalProcessingImpl::init fork failed %d", errno);
        return false;
    } else if (pid == 0) { // child
        if (execl(SimpleScriptExec::PYTHON_PATH, SimpleScriptExec::PYTHON_PATH,
                script.string(), NULL) < 0) {
            CHILD_LOGE("execl %s %s failed %d", SimpleScriptExec::PYTHON_PATH,
                    script.string(), errno);
            exit(EXIT_FAILURE);
        }
    } else { // parent
        mChildPid = pid;
        mChildRunning = true;
        int result = false;
        int retryCount = 0;
        // not that clean, but it takes some time for python side to have socket ready
        const int MAX_RETRY = 20;
        while (retryCount < MAX_RETRY) {
            usleep(CHILD_WAIT_TIME_US);
            mSocket.reset(new ClientSocket());
            if (mSocket.get() == NULL) {
                result = false;
                break;
            }
            if (mSocket->init("127.0.0.1", SCRIPT_PORT, true)) {
                result = true;
                break;
            }
            retryCount++;
        }
        if (!result) {
            LOGE("cannot connect to child");
            mSocket.reset(NULL);
            return result;
        }
    }
    return true;
}


TaskGeneric::ExecutionResult SignalProcessingImpl::run( const android::String8& functionScript,
        int nInputs, bool* inputTypes, void** inputs,
        int nOutputs, bool* outputTypes, void** outputs)
{
    mBuffer.reset();
    mBuffer.write <int32_t>((int32_t)EHeader);
    mBuffer.write<int32_t>(nInputs + 1);
    mBuffer.write<int32_t>((int32_t)EFunctionName);
    mBuffer.write<int32_t>((int32_t)functionScript.length());
    mBuffer.writeStr(functionScript);
    if (!send(mBuffer.getBuffer(), mBuffer.getSizeWritten())) {
        LOGE("send failed");
        return TaskGeneric::EResultError;
    }
    for (int i = 0; i < nInputs; i++) {
        mBuffer.reset();
        if (inputTypes[i]) { // android::sp<Buffer>*
            android::sp<Buffer>* buffer = reinterpret_cast<android::sp<Buffer>*>(inputs[i]);
            mBuffer.write<int32_t>((int32_t)((*buffer)->isStereo() ? EAudioStereo : EAudioMono));
            int dataLen = (*buffer)->getSize();
            mBuffer.write<int32_t>(dataLen);
            if (!send(mBuffer.getBuffer(), mBuffer.getSizeWritten())) {
                LOGE("send failed");
                return TaskGeneric::EResultError;
            }
            if (!send((*buffer)->getData(), dataLen)) {
                LOGE("send failed");
                return TaskGeneric::EResultError;
            }
            LOGD("%d-th param buffer %d, stereo:%d", dataLen, (*buffer)->isStereo());
        } else { //TaskCase::Value*
            TaskCase::Value* val = reinterpret_cast<TaskCase::Value*>(inputs[i]);
            bool isI64 = (val->getType() == TaskCase::Value::ETypeI64);
            mBuffer.write<int32_t>((int32_t)(isI64 ? EValue64Int : EValueDouble));
            if (isI64) {
                mBuffer.write<int64_t>(val->getInt64());
            } else  {
                mBuffer.write<double>(val->getDouble());
            }
            if (!send(mBuffer.getBuffer(), mBuffer.getSizeWritten())) {
                LOGE("send failed");
                return TaskGeneric::EResultError;
            }
            LOGD("%d-th param Value", i);
        }
    }
    int32_t header[4]; // id 0 - no of types - id 0x10 - ExecutionResult
    if (!read((char*)header, sizeof(header))) {
        LOGE("read failed");
        return TaskGeneric::EResultError;
    }
    if (header[0] != 0) {
        LOGE("wrong data");
        return TaskGeneric::EResultError;
    }
    if (header[2] != EExecutionResult) {
        LOGE("wrong data");
        return TaskGeneric::EResultError;
    }
    if (header[3] == TaskGeneric::EResultError) {
        LOGE("script returned error %d", header[3]);
        return (TaskGeneric::ExecutionResult)header[3];
    }
    if ((header[1] - 1) != nOutputs) {
        LOGE("wrong data");
        return TaskGeneric::EResultError;
    }
    for (int i = 0; i < nOutputs; i++) {
        int32_t type;
        if (!read((char*)&type, sizeof(type))) {
            LOGE("read failed");
            return TaskGeneric::EResultError;
        }
        if (outputTypes[i]) { // android::sp<Buffer>*
            int32_t dataLen;
            if (!read((char*)&dataLen, sizeof(dataLen))) {
                LOGE("read failed");
                return TaskGeneric::EResultError;
            }
            android::sp<Buffer>* buffer = reinterpret_cast<android::sp<Buffer>*>(outputs[i]);
            if (buffer->get() == NULL) { // data not allocated, this can happen for unknown-length output
                *buffer = new Buffer(dataLen, dataLen, (type == EAudioStereo) ? true: false);
                if (buffer->get() == NULL) {
                    LOGE("alloc failed");
                    return TaskGeneric::EResultError;
                }
            }
            bool isStereo = (*buffer)->isStereo();

            if (((type == EAudioStereo) && isStereo) || ((type == EAudioMono) && !isStereo)) {
                // valid
            } else {
                LOGE("%d-th output wrong type %d stereo: %d", i, type, isStereo);
                return TaskGeneric::EResultError;
            }

            if (dataLen > (int)(*buffer)->getSize()) {
                LOGE("%d-th output data too long %d while buffer size %d", i, dataLen,
                        (*buffer)->getSize());
                return TaskGeneric::EResultError;
            }
            if (!read((*buffer)->getData(), dataLen)) {
                LOGE("read failed");
                return TaskGeneric::EResultError;
            }
            LOGD("received buffer %x %x", ((*buffer)->getData())[0], ((*buffer)->getData())[1]);
            (*buffer)->setHandled(dataLen);
            (*buffer)->setSize(dataLen);
        } else { //TaskCase::Value*
            TaskCase::Value* val = reinterpret_cast<TaskCase::Value*>(outputs[i]);
            if ((type == EValue64Int) || (type == EValueDouble)) {
                if (!read((char*)val->getPtr(), sizeof(int64_t))) {
                    LOGE("read failed");
                    return TaskGeneric::EResultError;
                }
                if (type == EValue64Int) {
                    val->setType(TaskCase::Value::ETypeI64);
                } else {
                    val->setType(TaskCase::Value::ETypeDouble);
                }
            } else {
                LOGE("wrong type %d", type);
                return TaskGeneric::EResultError;
            }
        }
    }
    return (TaskGeneric::ExecutionResult)header[3];
}

bool SignalProcessingImpl::send(const char* data, int len)
{
    //LOGD("send %d", len);
    return mSocket->sendData(data, len);
}

bool SignalProcessingImpl::read(char* data, int len)
{
    const int READ_TIMEOUT_MS = 60000 * 2; // as some calculation like calc_delay takes almost 20 secs
    //LOGD("read %d", len);
    return mSocket->readData(data, len, READ_TIMEOUT_MS);
}