/*
  * Copyright 2010-2017 JetBrains s.r.o.
  *
  * 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 <cstdlib>

#include "KString.h"
#include "Memory.h"
#include "Natives.h"
#include "Porting.h"
#include "Runtime.h"
#include "Types.h"

#ifdef KONAN_ANDROID

#include <unistd.h>
#include <pthread.h>
#include <sys/errno.h>
#include <sys/types.h>
#include <sys/socket.h>

#include "launcher.h"
#include "androidLauncher.h"

#include <android/log.h>

#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "Konan_main", __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "Konan_main", __VA_ARGS__))

/* For debug builds, always enable the debug traces in this library */
#ifndef NDEBUG
#  define LOGV(...)  ((void)__android_log_print(ANDROID_LOG_VERBOSE, "Konan_main", __VA_ARGS__))
#else
#  define LOGV(...)  ((void)0)
#endif

using namespace kotlin;

//--- main --------------------------------------------------------------------//
namespace {

typedef struct {
    int pipeC;
    int pipeKonan;
    NativeActivityState nativeActivityState;
} LauncherState;

LauncherState* launcherState = nullptr;

}

extern "C" void getNativeActivityState(NativeActivityState* state) {
  state->activity = launcherState->nativeActivityState.activity;
  state->savedState = launcherState->nativeActivityState.savedState;
  state->savedStateSize = launcherState->nativeActivityState.savedStateSize;
  state->looper = launcherState->nativeActivityState.looper;
}

extern "C" void notifySysEventProcessed() {
  int8_t message;
  write(launcherState->pipeKonan, &message, sizeof(message));
}

namespace {

void launchMain() {
  Kotlin_initRuntimeIfNeeded();
  Kotlin_mm_switchThreadStateRunnable();

  {
    ObjHolder args;
    AllocArrayInstance(theArrayTypeInfo, 0, args.slot());
    Konan_start(args.obj());
  }

  // TODO: Can we shutdown runtime here?
  deinitRuntimeIfNeeded();
}

void* entry(void* param) {
  ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
  ALooper_addFd(looper, launcherState->pipeKonan, LOOPER_ID_SYS, ALOOPER_EVENT_INPUT, NULL, NULL);
  launcherState->nativeActivityState.looper = looper;

  launchMain();

  return nullptr;
}

void runKonan_start(bool startThread) {
  if (!startThread) {
     launchMain();
     return;
  }

  int pipes[2];
  if (socketpair(AF_UNIX, SOCK_STREAM, 0, pipes)) {
    LOGE("Could not create pipe: %s", strerror(errno));
    return;
  }
  launcherState->pipeC = pipes[0];
  launcherState->pipeKonan = pipes[1];

  LOGE("runKonan_start() %d %d", launcherState->pipeC, launcherState->pipeKonan);

  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  pthread_t thread;
  pthread_create(&thread, &attr, entry, nullptr);
}

void putEventSynchronously(void* event) {
  auto value = reinterpret_cast<uintptr_t>(event);

  if (write(launcherState->pipeC, &value, sizeof(value)) != sizeof(value)) {
    LOGE("Failure writing event: %s\n", strerror(errno));
  }
  int8_t response;
  if (read(launcherState->pipeC, &response, sizeof(response)) != sizeof(response)) {
    LOGE("Failure reading response: %s\n", strerror(errno));
  }
}

void onDestroy(ANativeActivity* activity) {
  LOGV("onDestroy called");
  NativeActivityEvent event = { DESTROY };
  putEventSynchronously(&event);
}

void onStart(ANativeActivity* activity) {
  LOGV("onStart called");
  NativeActivityEvent event = { START };
  putEventSynchronously(&event);
}

void onResume(ANativeActivity* activity) {
  LOGV("onResume called");
  NativeActivitySaveStateEvent event = { RESUME, nullptr, 0 };
  putEventSynchronously(&event);
}

void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen) {
  LOGV("onSaveInstanceState called");
  NativeActivitySaveStateEvent event = { SAVE_INSTANCE_STATE, nullptr, 0 };
  putEventSynchronously(&event);
  *outLen = event.savedStateSize;
  return event.savedState;
}

void onPause(ANativeActivity* activity) {
  LOGV("onPause called");
  NativeActivityEvent event = { PAUSE };
  putEventSynchronously(&event);
}

void onStop(ANativeActivity* activity) {
  LOGV("onStop called");
  NativeActivityEvent event = { STOP };
  putEventSynchronously(&event);
}

void onConfigurationChanged(ANativeActivity* activity) {
  LOGV("onConfigurationChanged called");
  NativeActivityEvent event = { CONFIGURATION_CHANGED };
  putEventSynchronously(&event);
}

void onLowMemory(ANativeActivity* activity) {
  LOGV("onLowMemory called");
  NativeActivityEvent event = { LOW_MEMORY };
  putEventSynchronously(&event);
}

void onWindowFocusChanged(ANativeActivity* activity, int focused) {
  LOGV("onWindowFocusChanged called");
  NativeActivityEvent event = { focused ? WINDOW_GAINED_FOCUS : WINDOW_LOST_FOCUS };
  putEventSynchronously(&event);
}

void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) {
  LOGV("onNativeWindowCreated called");
  NativeActivityWindowEvent event = { NATIVE_WINDOW_CREATED, window };
  putEventSynchronously(&event);
}

void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) {
  LOGV("onNativeWindowDestroyed called");
  NativeActivityWindowEvent event = { NATIVE_WINDOW_DESTROYED, window };
  putEventSynchronously(&event);
}

void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) {
  LOGV("onInputQueueCreated called");
  NativeActivityQueueEvent event = { INPUT_QUEUE_CREATED, queue };
  putEventSynchronously(&event);
}

void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) {
  LOGV("onInputQueueDestroyed called");
  NativeActivityQueueEvent event = { INPUT_QUEUE_DESTROYED, queue };
  putEventSynchronously(&event);
}
}

extern "C" void RUNTIME_EXPORT Konan_main(
    ANativeActivity* activity, void* savedState, size_t savedStateSize) {
  bool launchThread = activity->instance == nullptr;
  if (launchThread) {
    launcherState = (LauncherState*)std::calloc(sizeof(LauncherState), 1);
    launcherState->nativeActivityState = {activity, savedState, savedStateSize, nullptr};
    activity->instance = launcherState;
    activity->callbacks->onDestroy = onDestroy;
    activity->callbacks->onStart = onStart;
    activity->callbacks->onResume = onResume;
    activity->callbacks->onSaveInstanceState = onSaveInstanceState;
    activity->callbacks->onPause = onPause;
    activity->callbacks->onStop = onStop;
    activity->callbacks->onConfigurationChanged = onConfigurationChanged;
    activity->callbacks->onLowMemory = onLowMemory;
    activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
    activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
    activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
    activity->callbacks->onInputQueueCreated = onInputQueueCreated;
    activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;
  } else {
    launcherState = (LauncherState*)activity->instance;
  }

  runKonan_start(launchThread);
}

#endif // KONAN_ANDROID
