#include "engine/platform/platform.h"

#include <limits.h>
#include <stdio.h>
#include <cstring>
#include <memory>

#include "base/log.h"
#include "base/vecmath.h"
#include "engine/input_event.h"
#include "engine/platform/platform_observer.h"

using namespace base;

namespace eng {

void KaliberMain(Platform* platform);

Platform::Platform() {
  LOG(0) << "Initializing platform.";

  root_path_ = "./";
  data_path_ = "./";
  shared_data_path_ = "./";

  char dest[PATH_MAX];
  memset(dest, 0, sizeof(dest));
  if (readlink("/proc/self/exe", dest, PATH_MAX) > 0) {
    std::string path = dest;
    std::size_t last_slash_pos = path.find_last_of('/');
    if (last_slash_pos != std::string::npos)
      path = path.substr(0, last_slash_pos + 1);

    root_path_ = path;
    data_path_ = path;
    shared_data_path_ = path;
  }

  LOG(0) << "Root path: " << root_path_.c_str();
  LOG(0) << "Data path: " << data_path_.c_str();
  LOG(0) << "Shared data path: " << shared_data_path_.c_str();
}

void Platform::CreateMainWindow() {
  bool res = CreateWindow(800, 1205);
  CHECK(res) << "Failed to create window.";

  XSelectInput(display_, window_,
               KeyPressMask | Button1MotionMask | ButtonPressMask |
                   ButtonReleaseMask | FocusChangeMask | StructureNotifyMask);
  Atom WM_DELETE_WINDOW = XInternAtom(display_, "WM_DELETE_WINDOW", false);
  XSetWMProtocols(display_, window_, &WM_DELETE_WINDOW, 1);
}

Platform::~Platform() {
  LOG(0) << "Shutting down platform.";
  DestroyWindow();
}

void Platform::Update() {
  while (XPending(display_)) {
    XEvent e;
    XNextEvent(display_, &e);
    switch (e.type) {
      case KeyPress: {
        KeySym key = XLookupKeysym(&e.xkey, 0);
        auto input_event =
            std::make_unique<InputEvent>(InputEvent::kKeyPress, (char)key);
        observer_->AddInputEvent(std::move(input_event));
        // TODO: e.xkey.state & (ShiftMask | ControlMask | Mod1Mask | Mod4Mask))
        break;
      }
      case MotionNotify: {
        Vector2f v(e.xmotion.x, e.xmotion.y);
        auto input_event =
            std::make_unique<InputEvent>(InputEvent::kDrag, 0, v);
        observer_->AddInputEvent(std::move(input_event));
        break;
      }
      case ButtonPress: {
        if (e.xbutton.button == 1) {
          Vector2f v(e.xbutton.x, e.xbutton.y);
          auto input_event =
              std::make_unique<InputEvent>(InputEvent::kDragStart, 0, v);
          observer_->AddInputEvent(std::move(input_event));
        }
        break;
      }
      case ButtonRelease: {
        if (e.xbutton.button == 1) {
          Vector2f v(e.xbutton.x, e.xbutton.y);
          auto input_event =
              std::make_unique<InputEvent>(InputEvent::kDragEnd, 0, v);
          observer_->AddInputEvent(std::move(input_event));
        }
        break;
      }
      case FocusOut: {
        observer_->LostFocus();
        break;
      }
      case FocusIn: {
        observer_->GainedFocus(false);
        break;
      }
      case ClientMessage: {
        // WM_DELETE_WINDOW is the only registered type for now.
        observer_->OnWindowDestroyed();
        DestroyWindow();
        should_exit_ = true;
        return;
      }
      case ConfigureNotify: {
        XConfigureEvent xce = e.xconfigure;
        observer_->OnWindowResized(xce.width, xce.height);
      }
    }
  }
}

void Platform::Exit() {
  should_exit_ = true;
}

void Platform::Vibrate(int duration) {}

void Platform::ShowInterstitialAd() {}

void Platform::ShareFile(const std::string& file_name) {}

void Platform::SetKeepScreenOn(bool keep_screen_on) {}

bool Platform::CreateWindow(int width, int height) {
  // Try to open the local display.
  display_ = XOpenDisplay(NULL);
  if (!display_) {
    LOG(0) << "Can't connect to X server. Try to set the DISPLAY environment "
              "variable (hostname:number.screen_number).";
    return false;
  }

  Window root_window = DefaultRootWindow(display_);

  XVisualInfo* visual_info = GetXVisualInfo(display_);
  if (!visual_info) {
    LOG(0) << "No appropriate visual found.";
    return false;
  }
  LOG(0) << "Visual " << (void*)visual_info->visualid << " selected";

  // Create the main window.
  XSetWindowAttributes window_attributes;
  window_attributes.colormap =
      XCreateColormap(display_, root_window, visual_info->visual, AllocNone);
  window_attributes.event_mask = ExposureMask | KeyPressMask;
  window_ = XCreateWindow(display_, root_window, 0, 0, width, height, 0,
                          visual_info->depth, InputOutput, visual_info->visual,
                          CWColormap | CWEventMask, &window_attributes);
  XMapWindow(display_, window_);
  XStoreName(display_, window_, "kaliber");

  return true;
}

void Platform::DestroyWindow() {
  if (display_) {
    XDestroyWindow(display_, window_);
#if 0  // TODO: Figure out why XCloseDisplay is crashing
    XCloseDisplay(display_);
#endif
    display_ = nullptr;
    window_ = 0;
  }
}

Display* Platform::GetDisplay() {
  return display_;
}

Window Platform::GetWindow() {
  return window_;
}

XVisualInfo* Platform::GetXVisualInfo(Display* display) {
  long visual_mask = VisualScreenMask;
  int num_visuals;
  XVisualInfo visual_info_template = {};
  visual_info_template.screen = DefaultScreen(display);
  return XGetVisualInfo(display, visual_mask, &visual_info_template,
                        &num_visuals);
}

}  // namespace eng

int main(int argc, char** argv) {
  eng::Platform platform;
  eng::KaliberMain(&platform);
  return 0;
}
