#include "exports.h"


void InitCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  Uint32 flags = SDL_INIT_EVERYTHING;
  if(args[0]->IsUint32()) {
    flags = args[0].As<v8::Uint32>()->Value();
  }

  if (SDL_Init(flags) < 0) {
    printf("SDL_Init failed: %s\n", SDL_GetError());
  }

  TTF_Init();
  Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 4096);
  OverhypeCore::Init();
}

void DelayCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();
  if(!args[0]->IsUint32()) {
    OverhypeCore::Err(isolate, "Invalid arguments.\n\tRight args: ( uint obligatory )");
    return;
  }

  SDL_Delay(args[0].As<v8::Uint32>()->Value());
}

void CreateWindowCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();
  if(!args[0]->IsObject()) {
    OverhypeCore::Err(isolate, "Invalid arguments.\n\tRight args: ( obj obligatory )");
    return;
  }

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  v8::Local<v8::Object> obj = args[0]->ToObject(context).ToLocalChecked();
  v8::Local<v8::Array> props = obj->GetOwnPropertyNames(context).ToLocalChecked();

  std::string title = "";
  int x = SDL_WINDOWPOS_CENTERED, y = SDL_WINDOWPOS_CENTERED, w = 0, h = 0;
  Uint32 flags = 0;

  bool isWidthInitialized = 0;
  bool isHeghtInitialized = 0;

  Uint32 j = 0;
  for(Uint32 i = 0, l = props->Length(); i < l; i++) {
    v8::Local<v8::Value> localKey = props->Get(i);
    v8::Local<v8::Value> localVal = obj->Get(context, localKey).ToLocalChecked();
    std::string key = *v8::String::Utf8Value(localKey);
      
    if(key == "title") { //const char*
      title = *v8::String::Utf8Value(localVal);
      j++;
    } else if (key == "x") { //int
      x = localVal->IntegerValue();
      j++;
    } else if (key == "y") { //int
      y = localVal->IntegerValue();
      j++;
    } else if (key == "w") { //int
      w = localVal->IntegerValue();
      isWidthInitialized = 1;
      j++;
    } else if (key == "h") { //int
      h = localVal->IntegerValue();
      isHeghtInitialized = 1;
      j++;
    } else if (key == "flags") { //Uint32
      flags = localVal->Uint32Value();
      j++;
    }

    if(j == 6) {
      break;
    }
  }

  if(!isWidthInitialized || !isHeghtInitialized) {
    OverhypeCore::Err(isolate, "Invalid object.\n\tRight args: ( { title: string,\n\t\tx: int,\n\t\ty: int,\n\t\tw: int obligatory,\n\t\th: int obligatory,\n\t\tflags: uint,\n\t} )");
    return;
  }

  OverhypeCore::SetWindow(SDL_CreateWindow(title.c_str(), x, y, w, h, flags));
  SDL_Surface *surface = SDL_GetWindowSurface(OverhypeCore::GetWindow());
  Uint32 surfaceId = OverhypeCore::RegisterSurface(surface, true);
  args.GetReturnValue().Set(OverhypeCore::ReturnSurface(surfaceId, surface->w, surface->h));
}

void FillRectCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();
  if(!args[0]->IsObject()) {
    OverhypeCore::Err(isolate, "Invalid arguments.\n\tRight args: ( surface obligatory, uint )");
    return;
  }
  Uint32 color = 0xC97878;
  if(args[1]->IsUint32()) {
    color = args[1].As<v8::Uint32>()->Value();
  }
  
  SDL_FillRect(OverhypeCore::GetSurface(args[0]) , NULL, color);
}

void UpdateWindowSurfaceCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  SDL_UpdateWindowSurface(OverhypeCore::GetWindow());
}

void LoadFontCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();
  if(!args[0]->IsString()) {
    OverhypeCore::Err(isolate, "Invalid arguments.\n\tRight args: ( str obligatory, int )");
    return;
  }
  int size = 14;
  if(args[1]->IsInt32()) {
    size = args[1].As<v8::Integer>()->Value();
  }

  v8::String::Utf8Value str(args[0]);
  std::string path(*str);

  TTF_Font *font = TTF_OpenFont(path.c_str(), size);
  if(font == 0) OverhypeCore::Err(isolate, ("Can not find the file: " + path).c_str());

  Uint32 fontId = OverhypeCore::RegisterFont(font);
  v8::Local<v8::Integer> result = v8::Uint32::New(isolate, fontId);
  args.GetReturnValue().Set(result);
}

void BindFontCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();
  if(!args[0]->IsUint32()) {
    OverhypeCore::Err(isolate, "Invalid arguments.\n\tRight args: ( uint obligatory )");
    return;
  }
  OverhypeCore::BindFont(args[0].As<v8::Uint32>()->Value());
}

void LoadSurfaceCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();
  if(!args[0]->IsString()) {
    OverhypeCore::Err(isolate, "Invalid arguments.\n\tRight args: ( str obligatory )");
    return;
  }

  v8::String::Utf8Value str(args[0]);
  std::string path(*str);

  SDL_Surface *surface = IMG_Load(path.c_str());
  if(surface == 0) OverhypeCore::Err(isolate, ("Can not find the file: " + path).c_str());

  Uint32 surfaceId = OverhypeCore::RegisterSurface(surface);
  args.GetReturnValue().Set(OverhypeCore::ReturnSurface(surfaceId, surface->w, surface->h));
}

void BlitSurfaceCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();
  if(!args[0]->IsObject()) {
    OverhypeCore::Err(isolate, "Invalid arguments.\n\tRight args: ( surface obligatory, obj, surface obligatory, obj )");
    return;
  }
  
  SDL_Rect rect1; rect1.x = 0; rect1.y = 0; rect1.w = 0; rect1.h = 0;
  bool rect1undef = false;
  if(args[1]->IsObject()) {
    v8::Local<v8::Context> context = isolate->GetCurrentContext();
    v8::Local<v8::Object> obj = args[1]->ToObject(context).ToLocalChecked();
    v8::Local<v8::Array> props = obj->GetOwnPropertyNames(context).ToLocalChecked();

    for(Uint32 i = 0, l = props->Length(); i < l; i++) {
      v8::Local<v8::Value> localKey = props->Get(i);
      v8::Local<v8::Value> localVal = obj->Get(context, localKey).ToLocalChecked();
      std::string key = *v8::String::Utf8Value(localKey);
      
      if(key == "x") {
        rect1.x = localVal->IntegerValue();
      } else if (key == "y") { //int
        rect1.y = localVal->IntegerValue();
      } else if (key == "w") { //int
        rect1.w = localVal->IntegerValue();
      } else if (key == "h") { //int
        rect1.h = localVal->IntegerValue();
      }
    }
  } else {
    rect1undef = true;
  }
  
  if(!args[2]->IsObject()) {
    OverhypeCore::Err(isolate, "Invalid arguments.\n\tRight args: ( surface obligatory, obj, surface obligatory, obj )");
    return;
  }

  SDL_Rect rect2; rect2.x = 0; rect2.y = 0; rect2.w = 0; rect2.h = 0;
  bool rect2undef = false;
  if(args[3]->IsObject()) {
    v8::Local<v8::Context> context = isolate->GetCurrentContext();
    v8::Local<v8::Object> obj = args[3]->ToObject(context).ToLocalChecked();
    v8::Local<v8::Array> props = obj->GetOwnPropertyNames(context).ToLocalChecked();

    for(Uint32 i = 0, l = props->Length(); i < l; i++) {
      v8::Local<v8::Value> localKey = props->Get(i);
      v8::Local<v8::Value> localVal = obj->Get(context, localKey).ToLocalChecked();
      std::string key = *v8::String::Utf8Value(localKey);
      
      if(key == "x") {
        rect2.x = localVal->IntegerValue();
      } else if (key == "y") { //int
        rect2.y = localVal->IntegerValue();
      } else if (key == "w") { //int
        rect2.w = localVal->IntegerValue();
      } else if (key == "h") { //int
        rect2.h = localVal->IntegerValue();
      }
    }
  } else {
    rect2undef = true;
  }

  SDL_BlitSurface(
    OverhypeCore::GetSurface(args[0]),
    rect1undef ? 0 : &rect1,
    OverhypeCore::GetSurface(args[2]),
    rect2undef ? 0 : &rect2
  );
}




void QuitCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
    OverhypeCore::Quit();
    TTF_Quit();
    SDL_Quit();
}

void EventStartCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  std::thread *eventThread = new std::thread(OverhypeCore::HandleEvent);
}


void EventMouseCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();
  v8::Local<v8::Object> result = v8::Object::New(isolate);
  result->Set(v8::String::NewFromUtf8(isolate, "x"), v8::Integer::New(isolate, OverhypeCore::x));
  result->Set(v8::String::NewFromUtf8(isolate, "y"), v8::Integer::New(isolate, OverhypeCore::y));
  args.GetReturnValue().Set(result);
}

void EventPressedCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();
  v8::Local<v8::Boolean> result = v8::Boolean::New(isolate, OverhypeCore::GetKeyPressed());
  args.GetReturnValue().Set(result);
}

void EventCodeCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();
  if(!args[0]->IsUint32()) {
    v8::Local<v8::Boolean> result = v8::Boolean::New(isolate, false);
    args.GetReturnValue().Set(result);
    return;
  }

  Uint32 code = args[0].As<v8::Uint32>()->Value();
  v8::Local<v8::Boolean> result = v8::Boolean::New(isolate, OverhypeCore::GetKeyState(code));
  args.GetReturnValue().Set(result);
}

void EventReleasedCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();
  v8::Local<v8::Boolean> result = v8::Boolean::New(isolate, OverhypeCore::GetKeyReleased());
  args.GetReturnValue().Set(result);
}

void EventGetCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();
  //if(!args[0].IsFunction()) {
  //  return;
  //}
  OverhypeCore::SetEventCallback(isolate, v8::Local<v8::Function>::Cast(args[0]));
}

void SoundLoadCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();
  if(!args[0]->IsString()) {
    OverhypeCore::Err(isolate, "Invalid arguments.\n\tRight args: ( str obligatory )");
    return;
  }

  v8::String::Utf8Value str(args[0]);
  std::string path(*str);

  Mix_Chunk *music = Mix_LoadWAV(path.c_str());
  if(music == 0) OverhypeCore::Err(isolate, ("Can not find the file: " + path).c_str());

  Uint32 musicId = OverhypeCore::RegisterMusic(music);
  v8::Local<v8::Integer> result = v8::Uint32::New(isolate, musicId);
  args.GetReturnValue().Set(result);
}

void SoundChannelCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();
  v8::Local<v8::Object> result = v8::Object::New(isolate);
  result->Set(v8::String::NewFromUtf8(isolate, "id"), v8::Integer::New(isolate, -1));
  args.GetReturnValue().Set(result);
}

void SoundVolumeCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();

  if(!args[0]->IsObject()) {
    OverhypeCore::Err(isolate, "Invalid arguments.\n\tRight args: ( obj obligatory, int )");
    return;
  }

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  v8::Local<v8::Object> obj = args[0]->ToObject(context).ToLocalChecked();
  v8::Local<v8::Array> props = obj->GetOwnPropertyNames(context).ToLocalChecked();

  int channel;
  for(Uint32 i = 0, l = props->Length(); i < l; i++) {
    v8::Local<v8::Value> localKey = props->Get(i);
    v8::Local<v8::Value> localVal = obj->Get(context, localKey).ToLocalChecked();
    std::string key = *v8::String::Utf8Value(localKey);
      
    if(key == "id") { //const char*
      channel = localVal->IntegerValue();
      break;
    }
  }

  if(args[1]->IsNumber()) {
    Uint32 volume = args[1].As<v8::Integer>()->Value();
    Mix_Volume(channel, volume);
  }
}

void SoundPlayCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();
  if(!args[0]->IsUint32()) {
      OverhypeCore::Err(isolate, "Invalid arguments.\n\tRight args: ( uint obligatory, obj obligatory, int )");
      return;
  }

  if(!args[1]->IsObject()) {
    OverhypeCore::Err(isolate, "Invalid arguments.\n\tRight args: ( uint obligatory, obj obligatory, int )");
    return;
  }

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  v8::Local<v8::Object> obj = args[1]->ToObject(context).ToLocalChecked();
  v8::Local<v8::Array> props = obj->GetOwnPropertyNames(context).ToLocalChecked();

  int channel;
  for(Uint32 i = 0, l = props->Length(); i < l; i++) {
    v8::Local<v8::Value> localKey = props->Get(i);
    v8::Local<v8::Value> localVal = obj->Get(context, localKey).ToLocalChecked();
    std::string key = *v8::String::Utf8Value(localKey);
      
    if(key == "id") {
      channel = localVal->IntegerValue();
      break;
    }
  }

  int iterations = -1;
  if(args[2]->IsNumber()) {
      iterations = args[2].As<v8::Integer>()->Value();
  }
  
  channel = Mix_PlayChannel(channel, OverhypeCore::GetMusic(args[0]), iterations);

  obj->Set(v8::String::NewFromUtf8(isolate, "id"), v8::Integer::New(isolate, channel));
}

void SoundPlayingCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();

  if(!args[0]->IsObject()) {
    OverhypeCore::Err(isolate, "Invalid arguments.\n\tRight args: ( obj obligatory )");
    return;
  }

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  v8::Local<v8::Object> obj = args[0]->ToObject(context).ToLocalChecked();
  v8::Local<v8::Array> props = obj->GetOwnPropertyNames(context).ToLocalChecked();

  int channel;
  for(Uint32 i = 0, l = props->Length(); i < l; i++) {
    v8::Local<v8::Value> localKey = props->Get(i);
    v8::Local<v8::Value> localVal = obj->Get(context, localKey).ToLocalChecked();
    std::string key = *v8::String::Utf8Value(localKey);
      
    if(key == "id") { //const char*
      channel = localVal->IntegerValue();
      break;
    }
  }

  v8::Local<v8::Boolean> result = v8::Boolean::New(isolate, Mix_Playing(channel));
  args.GetReturnValue().Set(result);
}

void RegisterGlobal(v8::Local<v8::Object> exports) {
    NODE_SET_METHOD(exports, "init", InitCallback);
    NODE_SET_METHOD(exports, "delay", DelayCallback);
    NODE_SET_METHOD(exports, "createWindow", CreateWindowCallback);  
    NODE_SET_METHOD(exports, "fillRect", FillRectCallback);
    NODE_SET_METHOD(exports, "updateWindowSurface", UpdateWindowSurfaceCallback);
    NODE_SET_METHOD(exports, "loadFont", LoadFontCallback);
    NODE_SET_METHOD(exports, "bindFont", BindFontCallback);
    NODE_SET_METHOD(exports, "loadSurface", LoadSurfaceCallback);
    NODE_SET_METHOD(exports, "blitSurface", BlitSurfaceCallback);
    NODE_SET_METHOD(exports, "quit", QuitCallback);

    v8::Isolate* isolate = v8::Isolate::GetCurrent();
    v8::Local<v8::Object> event = v8::Object::New(isolate);
    exports->Set(v8::String::NewFromUtf8(isolate, "event"), event);

    NODE_SET_METHOD(event, "start", EventStartCallback);
    NODE_SET_METHOD(event, "mouse", EventMouseCallback);
    NODE_SET_METHOD(event, "pressed", EventPressedCallback);
    NODE_SET_METHOD(event, "code", EventCodeCallback);
    NODE_SET_METHOD(event, "released", EventReleasedCallback);
    NODE_SET_METHOD(event, "get", EventGetCallback);

    v8::Local<v8::Object> sound = v8::Object::New(isolate);
    exports->Set(v8::String::NewFromUtf8(isolate, "sound"), sound);

    NODE_SET_METHOD(sound, "load", SoundLoadCallback);
    NODE_SET_METHOD(sound, "channel", SoundChannelCallback);
    NODE_SET_METHOD(sound, "volume", SoundVolumeCallback);
    NODE_SET_METHOD(sound, "play", SoundPlayCallback);
    NODE_SET_METHOD(sound, "playing", SoundPlayingCallback);
}
