/**
 * 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.
 */

#include "script_bridge_in_multi_so.h"
#include <dlfcn.h>
#include <malloc.h>
#include "base/log_defines.h"

#include "android/bridge/multi_so_initializer.h"
#include "android/bridge/script/script_side_in_multi_so.h"
#include "android/utils/ipc_string_result.h"
#include "android/utils/params_utils.h"
#include "base/android/log_utils.h"
#include "base/make_copyable.h"
#include "base/thread/waitable_event.h"
#include "core/bridge/script/core_side_in_script.h"
#include "core/manager/weex_core_manager.h"

namespace WeexCore {

inline static char *copyStr(const char *str, int length = 0) {
  char *ret = nullptr;
  if (str == nullptr) return ret;
  int strLen = length == 0 ? strlen(str) : length;
  ret = new char[strLen + 1];
  memcpy(ret, str, static_cast<size_t>(strLen));
  ret[strLen] = '\0';
  return ret;
}

ScriptBridgeInMultiSo::ScriptBridgeInMultiSo() {
  set_core_side(new CoreSideInScript);
  set_script_side(new bridge::script::ScriptSideInMultiSo);
  std::unique_ptr<MultiSoInitializer> initializer(new MultiSoInitializer);
  bool passable = initializer->Init(
      [this](void *handle) {
        LOGE("dlopen so and call function");

        typedef FunctionsExposedByJS *(*ExchangeJSBridgeFunctions)(
            FunctionsExposedByCore *);
        auto exchange_script_bridge_functions =
            (ExchangeJSBridgeFunctions) dlsym(handle,
                                              "ExchangeJSBridgeFunctions");
        if (!exchange_script_bridge_functions) {
          return false;
        }

        FunctionsExposedByCore *platform_expose_functions =
            GetExposedFunctions();
        auto script_side_functions =
            exchange_script_bridge_functions(platform_expose_functions);

        static_cast<bridge::script::ScriptSideInMultiSo *>(script_side())
            ->set_script_side_functions(script_side_functions);
        return true;
      },
      [this](const char *status_code, const char *error_msg) {
        WeexCoreManager::Instance()
            ->getPlatformBridge()
            ->platform_side()
            ->ReportNativeInitStatus(status_code, error_msg);
      });
  set_is_passable(passable);
}

ScriptBridgeInMultiSo::~ScriptBridgeInMultiSo() {}

static void CallNative(const char *page_id, const char *task,
                       const char *callback) {
  //  WeexCoreManager::Instance()->script_bridge()->core_side()->CallNative(
  //      page_id, task, callback);

  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([page_id = std::string(page_id),
                                   task = std::string(task),
                                   callback = std::string(callback)] {
        WeexCoreManager::Instance()->script_bridge()->core_side()->CallNative(
            page_id.c_str(), task.c_str(), callback.c_str());
      }));
}

static std::unique_ptr<ValueWithType> CallNativeModule(
    const char *page_id, const char *module, const char *method,
    const char *arguments, int arguments_length, const char *options,
    int options_length) {
  //  auto ret = WeexCoreManager::Instance()
  //      ->script_bridge()
  //      ->core_side()
  //      ->CallNativeModule(page_id, module, method, arguments,
  //      arguments_length,
  //                         options, options_length);
  weex::base::WaitableEvent event;
  std::unique_ptr<ValueWithType> ret;
  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable(
          [page_id = std::string(page_id), module = std::string(module),
              method = std::string(method),
              arguments =
              std::unique_ptr<char[]>(copyStr(arguments, arguments_length)),
              arguments_length = arguments_length,
              options = std::unique_ptr<char[]>(copyStr(options, options_length)),
              options_length = options_length, e = &event, ret = &ret] {
            *ret = WeexCoreManager::Instance()
                ->script_bridge()
                ->core_side()
                ->CallNativeModule(page_id.c_str(), module.c_str(),
                                   method.c_str(), arguments.get(),
                                   arguments_length, options.get(),
                                   options_length);
            e->Signal();
          }));
  event.Wait();
  return ret;
}

static void CallNativeComponent(const char *page_id, const char *ref,
                                const char *method, const char *arguments,
                                int arguments_length, const char *options,
                                int options_length) {
  //  WeexCoreManager::Instance()
  //      ->script_bridge()
  //      ->core_side()
  //      ->CallNativeComponent(page_id, ref, method, arguments,
  //      arguments_length,
  //                            options, options_length);

  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable(
          [page_id = std::string(page_id), ref = std::string(ref),
              method = std::string(method),
              arguments =
              std::unique_ptr<char[]>(copyStr(arguments, arguments_length)),
              arguments_length = arguments_length,
              options = std::unique_ptr<char[]>(copyStr(options, options_length)),
              options_length = options_length] {
            WeexCoreManager::Instance()
                ->script_bridge()
                ->core_side()
                ->CallNativeComponent(page_id.c_str(), ref.c_str(),
                                      method.c_str(), arguments.get(),
                                      arguments_length, options.get(),
                                      options_length);
          }));
}

static void AddElement(const char *page_id, const char *parent_ref,
                       const char *dom_str, int domLen, const char *index_str) {
  //  WeexCoreManager::Instance()->script_bridge()->core_side()->AddElement(
  //      page_id, parent_ref, dom_str, domLen, index_str);

  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([page_id = std::string(page_id),
                                   parent_ref = std::string(parent_ref),
                                   dom_str = std::unique_ptr<char[]>(
                                       copyStr(dom_str, domLen)),
                                   dom_len = domLen,
                                   index_str = std::string(index_str)] {
        WeexCoreManager::Instance()->script_bridge()->core_side()->AddElement(
            page_id.c_str(), parent_ref.c_str(), dom_str.get(), dom_len,
            index_str.c_str());
      }));
}

static void SetTimeout(const char *callback_id, const char *time) {
  //  WeexCoreManager::Instance()->script_bridge()->core_side()->SetTimeout(
  //      callback_id, time);

  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([callback_id = std::string(callback_id),
                                   time = std::string(time)] {
        WeexCoreManager::Instance()->script_bridge()->core_side()->SetTimeout(
            callback_id.c_str(), time.c_str());
      }));
}

static void NativeLog(const char *str_array) {
  //  WeexCoreManager::Instance()->script_bridge()->core_side()->NativeLog(
  //      str_array);

  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([str_array = std::string(str_array)] {
        WeexCoreManager::Instance()->script_bridge()->core_side()->NativeLog(
            str_array.c_str());
      }));
}

static void CreateBody(const char *page_id, const char *dom_str, int domLen) {
  //  WeexCoreManager::Instance()->script_bridge()->core_side()->CreateBody(
  //      page_id, dom_str, domLen);

  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([page_id = std::string(page_id),
                                   dom_str = std::unique_ptr<char[]>(
                                       copyStr(dom_str, domLen)),
                                   dom_len = domLen] {
        WeexCoreManager::Instance()->script_bridge()->core_side()->CreateBody(
            page_id.c_str(), dom_str.get(), dom_len);
      }));
}

static int UpdateFinish(const char *page_id, const char *task, int taskLen,
                        const char *callback, int callbackLen) {
  //  return WeexCoreManager::Instance()
  //      ->script_bridge()
  //      ->core_side()
  //      ->UpdateFinish(page_id, task, taskLen, callback, callbackLen);

  weex::base::WaitableEvent event;
  int result = -1;
  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable(
          [page_id = std::string(page_id),
              task = std::unique_ptr<char[]>(copyStr(task, taskLen)),
              task_len = taskLen,
              callback = std::unique_ptr<char[]>(copyStr(callback, callbackLen)),
              callback_len = callbackLen, event = &event, result = &result] {
            *result = WeexCoreManager::Instance()
                ->script_bridge()
                ->core_side()
                ->UpdateFinish(page_id.c_str(), task.get(), task_len,
                               callback.get(), callback_len);
            event->Signal();
          }));
  event.Wait();
  return result;
}

static void CreateFinish(const char *page_id) {
  //  WeexCoreManager::Instance()->script_bridge()->core_side()->CreateFinish(
  //      page_id);
  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([page_id = std::string(page_id)] {
        WeexCoreManager::Instance()->script_bridge()->core_side()->CreateFinish(
            page_id.c_str());
      }));
}

static int RefreshFinish(const char *page_id, const char *task,
                         const char *callback) {
  //  return WeexCoreManager::Instance()
  //      ->script_bridge()
  //      ->core_side()
  //      ->RefreshFinish(page_id, task, callback);

  int result = -1;
  weex::base::WaitableEvent event;
  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable(
          [page_id = std::string(page_id), task = std::string(task),
              callback = std::string(callback), event = &event] {
            WeexCoreManager::Instance()
                ->script_bridge()
                ->core_side()
                ->RefreshFinish(page_id.c_str(), task.c_str(),
                                callback.c_str());
            event->Signal();
          }));
  event.Wait();

  return result;
}

static void UpdateAttrs(const char *page_id, const char *ref, const char *data,
                        int dataLen) {
  //  WeexCoreManager::Instance()->script_bridge()->core_side()->UpdateAttrs(
  //      page_id, ref, data, dataLen);
  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([page_id = std::string(page_id),
                                   ref = std::string(ref),
                                   data = std::unique_ptr<char[]>(
                                       copyStr(data, dataLen)),
                                   data_len = dataLen] {
        WeexCoreManager::Instance()->script_bridge()->core_side()->UpdateAttrs(
            page_id.c_str(), ref.c_str(), data.get(), data_len);
      }));
}

static void UpdateStyle(const char *page_id, const char *ref, const char *data,
                        int dataLen) {
  //  WeexCoreManager::Instance()->script_bridge()->core_side()->UpdateStyle(
  //      page_id, ref, data, dataLen);
  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([page_id = std::string(page_id),
                                   ref = std::string(ref),
                                   data = std::unique_ptr<char[]>(
                                       copyStr(data, dataLen)),
                                   data_len = dataLen] {
        WeexCoreManager::Instance()->script_bridge()->core_side()->UpdateStyle(
            page_id.c_str(), ref.c_str(), data.get(), data_len);
      }));
}

static void RemoveElement(const char *page_id, const char *ref) {
  //  WeexCoreManager::Instance()->script_bridge()->core_side()->RemoveElement(
  //      page_id, ref);
  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable(
          [page_id = std::string(page_id), ref = std::string(ref)] {
            WeexCoreManager::Instance()
                ->script_bridge()
                ->core_side()
                ->RemoveElement(page_id.c_str(), ref.c_str());
          }));
}

static void MoveElement(const char *page_id, const char *ref,
                        const char *parent_ref, int index) {
  //  WeexCoreManager::Instance()->script_bridge()->core_side()->MoveElement(
  //      page_id, ref, parent_ref, index);
  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([page_id = std::string(page_id),
                                   ref = std::string(ref),
                                   parent_ref = std::string(parent_ref),
                                   index = index] {
        WeexCoreManager::Instance()->script_bridge()->core_side()->MoveElement(
            page_id.c_str(), ref.c_str(), parent_ref.c_str(), index);
      }));
}

static void AddEvent(const char *page_id, const char *ref, const char *event) {
  //  WeexCoreManager::Instance()->script_bridge()->core_side()->AddEvent(
  //      page_id, ref, event);
  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([page_id = std::string(page_id),
                                   ref = std::string(ref),
                                   event = std::string(event)] {
        WeexCoreManager::Instance()->script_bridge()->core_side()->AddEvent(
            page_id.c_str(), ref.c_str(), event.c_str());
      }));
}

static void RemoveEvent(const char *page_id, const char *ref,
                        const char *event) {
  //  WeexCoreManager::Instance()->script_bridge()->core_side()->RemoveEvent(
  //      page_id, ref, event);
  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([page_id = std::string(page_id),
                                   ref = std::string(ref),
                                   event = std::string(event)] {
        WeexCoreManager::Instance()->script_bridge()->core_side()->RemoveEvent(
            page_id.c_str(), ref.c_str(), event.c_str());
      }));
}
static const char *CallGCanvasLinkNative(const char *context_id, int type,
                                         const char *arg) {

  weex::base::WaitableEvent event;
  char *ret = nullptr;

  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([page_id = std::string(context_id),
                                   t = type,
                                   args = std::string(arg), e = &event, ret = &ret] {
        *ret = const_cast<char *>(WeexCoreManager::Instance()
            ->script_bridge()
            ->core_side()
            ->CallGCanvasLinkNative(page_id.c_str(), t, args.c_str()));
        e->Signal();
      }));
  event.Wait();

  return ret;
}

static int SetInterval(const char *page_id, const char *callback_id,
                       const char *time) {
  return WeexCoreManager::Instance()->script_bridge()->core_side()->SetInterval(
      page_id, callback_id, time);
}

static void ClearInterval(const char *page_id, const char *callback_id) {
  //  WeexCoreManager::Instance()->script_bridge()->core_side()->ClearInterval(
  //      page_id, callback_id);

  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([page_id = std::string(page_id),
                                   callback_id = std::string(callback_id)] {
        WeexCoreManager::Instance()
            ->script_bridge()
            ->core_side()
            ->ClearInterval(page_id.c_str(), callback_id.c_str());
      }));
}

static const char *CallT3DLinkNative(int type, const char *arg) {

  weex::base::WaitableEvent event;
  char *ret = nullptr;

  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([
                                   t = type,
                                   args = std::string(arg), e = &event, ret = &ret] {
        *ret = const_cast<char *>(WeexCoreManager::Instance()
            ->script_bridge()
            ->core_side()
            ->CallT3DLinkNative(t, args.c_str()));
        e->Signal();
      }));
  event.Wait();

  return ret;
}

static void PostMessage(const char *vim_id, const char *data, int dataLength) {
  //  WeexCoreManager::Instance()->script_bridge()->core_side()->PostMessage(vim_id,
  //                                                                         data);
  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([vim_id = std::string(vim_id),
                                   data = std::string(data), length = dataLength] {
        WeexCoreManager::Instance()->script_bridge()->core_side()->PostMessage(
            vim_id.c_str(), data.c_str(), length);
      }));
}

static void DispatchMessage(const char *client_id, const char *data, int dataLength,
                            const char *callback, const char *vm_id) {
  //  WeexCoreManager::Instance()->script_bridge()->core_side()->DispatchMessage(
  //      client_id, data, callback, vm_id);
  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable(
          [client_id = std::string(client_id), data = std::string(data),
              callback = std::string(callback), vm_id = std::string(vm_id), length = dataLength] {
            WeexCoreManager::Instance()
                ->script_bridge()
                ->core_side()
                ->DispatchMessage(client_id.c_str(), data.c_str(), length,
                                  callback.c_str(), vm_id.c_str());
          }));
}

static std::unique_ptr<WeexJSResult> DispatchMessageSync(const char *client_id,
                                                         const char *data,
                                                         int dataLength,
                                                         const char *vm_id) {
  weex::base::WaitableEvent event;
  std::unique_ptr<WeexJSResult> result;
  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([client_id = std::string(client_id),
                                   data = std::string(data),
                                   vm_id = std::string(vm_id), length = dataLength,
                                   e = &event, r = &result]() {
        *r = WeexCoreManager::Instance()
            ->script_bridge()
            ->core_side()
            ->DispatchMessageSync(client_id.c_str(), data.c_str(), length,
                                  vm_id.c_str());
        e->Signal();
      }));
  event.Wait();
  return result;
}

static void OnReceivedResult(long callback_id,
                             std::unique_ptr<WeexJSResult> &result) {
  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([callback_id, result = std::move(result)]() {
        WeexCoreManager::Instance()
            ->script_bridge()
            ->core_side()
            ->OnReceivedResult(
                callback_id,
                const_cast<std::unique_ptr<WeexJSResult> &>(result));
      }));
}

static void UpdateComponentData(const char *page_id,
                                const char *cid,
                                const char *json_data) {
  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable(
          [page_id = std::string(page_id), cid = std::string(cid),
              json_data = std::string(json_data)]() {
            WeexCoreManager::Instance()
                ->script_bridge()
                ->core_side()
                ->UpdateComponentData(page_id.c_str(), cid.c_str(), json_data.c_str());
          }));
}

static bool Log(int level, const char *tag,
                const char *file,
                unsigned long line,
                const char *log) {
  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable(
          [l = level, t = std::string(tag), f = std::string(file), li = line,
              log_str = std::string(log)]() {
            weex::base::LogImplement::getLog()->log((LogLevel) l,
                                                    t.c_str(),
                                                    f.c_str(),
                                                    li,
                                                    log_str.c_str());
          }));

  return true;
}

static void ReportException(const char *page_id, const char *func,
                            const char *exception_string) {
  //  WeexCoreManager::Instance()->script_bridge()->core_side()->ReportException(
  //      page_id, func, exception_string);
  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([page_id = std::string(page_id),
                                   func = std::string(func),
                                   exception = std::string(exception_string)] {
        WeexCoreManager::Instance()
            ->script_bridge()
            ->core_side()
            ->ReportException(page_id.c_str(), func.c_str(), exception.c_str());
      }));
}

static void SetJSVersion(const char *js_version) {
  //  WeexCoreManager::Instance()->script_bridge()->core_side()->SetJSVersion(
  //      js_version);
  WeexCoreManager::Instance()->script_thread()->message_loop()->PostTask(
      weex::base::MakeCopyable([version = std::string(js_version)] {
        WeexCoreManager::Instance()->script_bridge()->core_side()->SetJSVersion(
            version.c_str());
      }));
}

FunctionsExposedByCore *ScriptBridgeInMultiSo::GetExposedFunctions() {
  FunctionsExposedByCore temp = {SetJSVersion,
                                 ReportException,
                                 CallNative,
                                 CallNativeModule,
                                 CallNativeComponent,
                                 AddElement,
                                 SetTimeout,
                                 NativeLog,
                                 CreateBody,
                                 UpdateFinish,
                                 CreateFinish,
                                 RefreshFinish,
                                 UpdateAttrs,
                                 UpdateStyle,
                                 RemoveElement,
                                 MoveElement,
                                 AddEvent,
                                 RemoveEvent,
                                 SetInterval,
                                 ClearInterval,
                                 CallGCanvasLinkNative,
                                 CallT3DLinkNative,
                                 PostMessage,
                                 DispatchMessage,
                                 DispatchMessageSync,
                                 OnReceivedResult,
                                 UpdateComponentData,
                                 Log};
  auto functions =
      (FunctionsExposedByCore *) malloc(sizeof(FunctionsExposedByCore));
  memset(functions, 0, sizeof(FunctionsExposedByCore));
  memcpy(functions, &temp, sizeof(FunctionsExposedByCore));
  return functions;
}
}  // namespace WeexCore
