#ifdef HAVE_CONFIG_H
#include "../../config.h"
#endif

#include "../../config.def.h"

#ifdef HAVE_LIBNX
#include <switch.h>
#else
#include <libtransistor/nx.h>
#endif

#include "../configuration.h"

#include "../../tasks/tasks_internal.h"

#include "../../retroarch.h"
#include "../../command.h"
/* TODO/FIXME - weird header include */
#include "string.h"

/* TODO/FIXME - global referenced outside */
extern uint64_t lifecycle_state;

/* TODO/FIXME - static globals */
static uint16_t button_state[DEFAULT_MAX_PADS];
static int16_t analog_state[DEFAULT_MAX_PADS][2][2];
#ifdef HAVE_LIBNX
static PadState pad_states[DEFAULT_MAX_PADS];
static HidVibrationDeviceHandle vibration_handles[DEFAULT_MAX_PADS][2];
static HidVibrationDeviceHandle vibration_handleheld[2];
static HidVibrationValue vibration_values[DEFAULT_MAX_PADS][2];
static HidVibrationValue vibration_stop;
static int previous_handheld                         = -1; 
/* 1 = handheld, 0 = docked, -1 = first use */
static uint previous_split_joycon_setting[MAX_USERS] = { 0 };
#endif

static const char *switch_joypad_name(unsigned pad)
{
   return "Switch Controller";
}

static void switch_joypad_autodetect_add(unsigned autoconf_pad)
{
   input_autoconfigure_connect(
            switch_joypad_name(autoconf_pad), /* name */
            NULL,                             /* display name */
            switch_joypad.ident,              /* driver */
            autoconf_pad,                     /* idx */
            0,                                /* vid */
            0);                               /* pid */
}

static void *switch_joypad_init(void *data)
{
#ifdef HAVE_LIBNX
   unsigned i;
   hidSetNpadJoyHoldType(HidNpadJoyHoldType_Horizontal);
   padConfigureInput(DEFAULT_MAX_PADS, HidNpadStyleSet_NpadStandard);

   /* Switch like stop behavior with muted band channels 
    * and frequencies set to default. */
   vibration_stop.amp_low   = 0.0f;
   vibration_stop.freq_low  = 160.0f;
   vibration_stop.amp_high  = 0.0f;
   vibration_stop.freq_high = 320.0f;

   for (i = 0; i < DEFAULT_MAX_PADS; i++)
   {
      if (i == 0)
         padInitializeDefault(&pad_states[0]);
      else
         padInitialize(&pad_states[i], i);

      padUpdate(&pad_states[i]);
      switch_joypad_autodetect_add(i);
      hidInitializeVibrationDevices(
            vibration_handles[i], 2, i,
            HidNpadStyleTag_NpadHandheld | HidNpadStyleTag_NpadJoyDual);
      memcpy(&vibration_values[i][0],
            &vibration_stop, sizeof(HidVibrationValue));
      memcpy(&vibration_values[i][1],
            &vibration_stop, sizeof(HidVibrationValue));
   }
   hidInitializeVibrationDevices(vibration_handleheld,
         2, HidNpadIdType_Handheld, HidNpadStyleTag_NpadHandheld | HidNpadStyleTag_NpadJoyDual);
#else
   hid_init();
   switch_joypad_autodetect_add(0);
   switch_joypad_autodetect_add(1);
#endif

   return (void*)-1;
}

static int32_t switch_joypad_button(unsigned port_num, uint16_t joykey)
{
   if (port_num >= DEFAULT_MAX_PADS)
      return 0;
   return (button_state[port_num] & (1 << joykey));
}

static void switch_joypad_get_buttons(unsigned port_num, input_bits_t *state)
{
   if (port_num < DEFAULT_MAX_PADS)
   {
      BITS_COPY16_PTR(state, button_state[port_num]);
   }
   else
   {
      BIT256_CLEAR_ALL_PTR(state);
   }
}

static int16_t switch_joypad_axis_state(unsigned port, uint32_t joyaxis)
{
   if (AXIS_NEG_GET(joyaxis) < 4)
   {
      int16_t val  = 0;
      int16_t axis = AXIS_NEG_GET(joyaxis);
      switch (axis)
      {
         case 0:
         case 1:
            val = analog_state[port][0][axis];
            break;
         case 2:
         case 3:
            val = analog_state[port][1][axis - 2];
            break;
      }
      if (val < 0)
         return val;
   }
   else if (AXIS_POS_GET(joyaxis) < 4)
   {
      int16_t val   = 0;
      int16_t axis  = AXIS_POS_GET(joyaxis);
      switch (axis)
      {
         case 0:
         case 1:
            val = analog_state[port][0][axis];
            break;
         case 2:
         case 3:
            val = analog_state[port][1][axis - 2];
            break;
      }
      if (val > 0)
         return val;
   }
   return 0;
}

static int16_t switch_joypad_axis(unsigned port, uint32_t joyaxis)
{
   if (port >= DEFAULT_MAX_PADS)
      return 0;
   return switch_joypad_axis_state(port, joyaxis);
}

static int16_t switch_joypad_state(
      rarch_joypad_info_t *joypad_info,
      const struct retro_keybind *binds,
      unsigned port)
{
   unsigned i;
   int16_t ret                          = 0;
   uint16_t port_idx                    = joypad_info->joy_idx;

   if (port_idx >= DEFAULT_MAX_PADS)
      return 0;

   for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++)
   {
      /* Auto-binds are per joypad, not per user. */
      const uint64_t joykey  = (binds[i].joykey != NO_BTN)
         ? binds[i].joykey  : joypad_info->auto_binds[i].joykey;
      const uint32_t joyaxis = (binds[i].joyaxis != AXIS_NONE)
         ? binds[i].joyaxis : joypad_info->auto_binds[i].joyaxis;
      if (
               (uint16_t)joykey != NO_BTN 
            && (button_state[port_idx] & (1 << (uint16_t)joykey))
         )
         ret |= ( 1 << i);
      else if (joyaxis != AXIS_NONE &&
            ((float)abs(switch_joypad_axis_state(port_idx, joyaxis)) 
             / 0x8000) > joypad_info->axis_threshold)
         ret |= (1 << i);
   }

   return ret;
}

static bool switch_joypad_query_pad(unsigned pad)
{
   return pad < DEFAULT_MAX_PADS && button_state[pad];
}

static void switch_joypad_destroy(void)
{
#ifdef HAVE_LIBNX
   unsigned i;

   previous_handheld = -1;

   for (i = 0; i < MAX_USERS; i++)
      previous_split_joycon_setting[i] = 0;

   for (i = 0; i < DEFAULT_MAX_PADS; i++)
   {
      memcpy(&vibration_values[i][0],
            &vibration_stop, sizeof(HidVibrationValue));
      memcpy(&vibration_values[i][1],
            &vibration_stop, sizeof(HidVibrationValue));
      hidSendVibrationValues(vibration_handles[i], vibration_values[i], 2);
   }
   hidSendVibrationValues(vibration_handleheld, vibration_values[0], 2);
#else
   hid_finalize();
#endif
}

#ifdef HAVE_LIBNX

static void switch_joypad_poll(void)
{
   int i, handheld;
   settings_t *settings = config_get_ptr();

   for(i = 0; i < DEFAULT_MAX_PADS; i++)
      padUpdate(&pad_states[i]);

   handheld = padIsHandheld(&pad_states[0]);

   if (previous_handheld == -1)
   {
      /* First call of this function, apply joycon settings 
       * according to preferences, init variables */
      if (!handheld)
      {
         for (i = 0; i < MAX_USERS; i += 2)
         {
            unsigned input_split_joycon = 
               settings->uints.input_split_joycon[i];

            if (input_split_joycon)
            {
               hidSetNpadJoyAssignmentModeSingleByDefault(i);
               hidSetNpadJoyAssignmentModeSingleByDefault(i + 1);
            } 
            else if (!input_split_joycon)
            {
               hidSetNpadJoyAssignmentModeDual(i);
               hidSetNpadJoyAssignmentModeDual(i + 1);
               hidMergeSingleJoyAsDualJoy(i, i + 1);
            }
         }
      }
      previous_handheld = handheld;
      for (i = 0; i < MAX_USERS; i += 2)
         previous_split_joycon_setting[i] = settings->uints.input_split_joycon[i];
   }

   if (!handheld && previous_handheld)
   {
      /* switching out of handheld, so make sure 
       * joycons are correctly split. */
      for (i = 0; i < MAX_USERS; i += 2)
      {
         unsigned input_split_joycon = settings->uints.input_split_joycon[i];

         /* CONTROLLER_PLAYER_X, X == i++ */
         if (input_split_joycon)
         {
            hidSetNpadJoyAssignmentModeSingleByDefault(i);
            hidSetNpadJoyAssignmentModeSingleByDefault(i + 1);
         }
         else
         {
            hidSetNpadJoyAssignmentModeDual(i);
            hidSetNpadJoyAssignmentModeDual(i + 1);
            hidMergeSingleJoyAsDualJoy(i, i + 1);
         }
      }
   }
   else if (!handheld)
   {
      /* split or join joycons every time the user changes a setting */
      for (i = 0; i < MAX_USERS; i += 2)
      {
         unsigned input_split_joycon = settings->uints.input_split_joycon[i];
         if (input_split_joycon
               && !previous_split_joycon_setting[i])
         {
            hidSetNpadJoyAssignmentModeSingleByDefault(i);
            hidSetNpadJoyAssignmentModeSingleByDefault(i + 1);
         } 
         else if (!input_split_joycon
               && previous_split_joycon_setting[i])
         {
            hidSetNpadJoyAssignmentModeDual(i);
            hidSetNpadJoyAssignmentModeDual(i + 1);
            hidMergeSingleJoyAsDualJoy(i, i + 1);
         }
      }
   }

   for (i = 0; i < MAX_USERS; i += 2)
      previous_split_joycon_setting[i] = settings->uints.input_split_joycon[i];
   
   previous_handheld = handheld;

   for (i = 0; i < DEFAULT_MAX_PADS; i++)
   {
      HidAnalogStickState stick_left_state = padGetStickPos(&pad_states[i], 0);
      HidAnalogStickState stick_right_state = padGetStickPos(&pad_states[i], 1);
      unsigned input_split_joycon = settings->uints.input_split_joycon[i];
      int pad_button = padGetButtons(&pad_states[i]);
      if (input_split_joycon && !handheld) {
         button_state[i] = 0;

         if (hidGetNpadDeviceType((HidNpadIdType)i) & HidDeviceTypeBits_JoyLeft) {
            if (pad_button & HidNpadButton_Left)
               button_state[i] |= (uint16_t)HidNpadButton_B;
            if (pad_button & HidNpadButton_Up)
               button_state[i] |= (uint16_t)HidNpadButton_Y;
            if (pad_button & HidNpadButton_Right)
               button_state[i] |= (uint16_t)HidNpadButton_X;
            if (pad_button & HidNpadButton_Down)
               button_state[i] |= (uint16_t)HidNpadButton_A;
            if (pad_button & HidNpadButton_LeftSL)
               button_state[i] |= (uint16_t)HidNpadButton_L;
            if (pad_button & HidNpadButton_LeftSR)
               button_state[i] |= (uint16_t)HidNpadButton_R;
            if (pad_button & HidNpadButton_StickL)
               button_state[i] |= (uint16_t)HidNpadButton_StickL;
            if (pad_button & HidNpadButton_Minus)
               button_state[i] |= (uint16_t)HidNpadButton_Plus;

            analog_state[i][RETRO_DEVICE_INDEX_ANALOG_LEFT][RETRO_DEVICE_ID_ANALOG_X] = -stick_left_state.y;
            analog_state[i][RETRO_DEVICE_INDEX_ANALOG_LEFT][RETRO_DEVICE_ID_ANALOG_Y] = -stick_left_state.x;
         } else if (hidGetNpadDeviceType((HidNpadIdType)i) & HidDeviceTypeBits_JoyRight) {
            if (pad_button & HidNpadButton_A)
               button_state[i] |= (uint16_t)HidNpadButton_B;
            if (pad_button & HidNpadButton_B)
               button_state[i] |= (uint16_t)HidNpadButton_Y;
            if (pad_button & HidNpadButton_X)
               button_state[i] |= (uint16_t)HidNpadButton_A;
            if (pad_button & HidNpadButton_Y)
               button_state[i] |= (uint16_t)HidNpadButton_X;
            if (pad_button & HidNpadButton_RightSL)
               button_state[i] |= (uint16_t)HidNpadButton_L;
            if (pad_button & HidNpadButton_RightSR)
               button_state[i] |= (uint16_t)HidNpadButton_R;
            if (pad_button & HidNpadButton_StickR)
               button_state[i] |= (uint16_t)HidNpadButton_StickL;
            if (pad_button & HidNpadButton_Plus)
               button_state[i] |= (uint16_t)HidNpadButton_Plus;

            /* Throw JoyRight state into retro left analog */
            analog_state[i][RETRO_DEVICE_INDEX_ANALOG_LEFT][RETRO_DEVICE_ID_ANALOG_X] = stick_right_state.y;
            analog_state[i][RETRO_DEVICE_INDEX_ANALOG_LEFT][RETRO_DEVICE_ID_ANALOG_Y] = stick_right_state.x;
         } else {
            /* Handle other types via Default Input Handling */
            goto lblDefaultInputHandling;
         }
      }
      else
      {
         /* Default Input Handling */
lblDefaultInputHandling:
         button_state[i] = pad_button;
         analog_state[i][RETRO_DEVICE_INDEX_ANALOG_LEFT][RETRO_DEVICE_ID_ANALOG_X] = stick_left_state.x;
         analog_state[i][RETRO_DEVICE_INDEX_ANALOG_LEFT][RETRO_DEVICE_ID_ANALOG_Y] = -stick_left_state.y;
         
         analog_state[i][RETRO_DEVICE_INDEX_ANALOG_RIGHT][RETRO_DEVICE_ID_ANALOG_X] = stick_right_state.x;
         analog_state[i][RETRO_DEVICE_INDEX_ANALOG_RIGHT][RETRO_DEVICE_ID_ANALOG_Y] = -stick_right_state.y;
      }
   }
}
#else
static void switch_joypad_poll(void)
{
   int16_t lsx, lsy, rsx, rsy;
   hid_controller_t    *controllers  = hid_get_shared_memory()->controllers;
   hid_controller_t           *cont  = &controllers[0];
   hid_controller_state_entry_t ent  = cont->main.entries[cont->main.latest_idx];
   hid_controller_state_entry_t ent8 = (cont+8)->main.entries[(cont+8)->main.latest_idx];
   button_state[0]                   = ent.button_state | ent8.button_state;

   lsx                               = ent.left_stick_x;
   lsy                               = ent.left_stick_y;
   rsx                               = ent.right_stick_x;
   rsy                               = ent.right_stick_y;

   if (ent8.left_stick_x != 0 || ent8.left_stick_y != 0)
   {
      /* handheld overrides player 1 */
      lsx                            = ent8.left_stick_x;
      lsy                            = ent8.left_stick_y;
   }

   if (ent8.right_stick_x != 0 || ent8.right_stick_y != 0)
   {
      /* handheld overrides player 1 */
      rsx                            = ent8.right_stick_x;
      rsy                            = ent8.right_stick_y;
   }

   analog_state[0][RETRO_DEVICE_INDEX_ANALOG_LEFT]
      [RETRO_DEVICE_ID_ANALOG_X]     = lsx;
   analog_state[0][RETRO_DEVICE_INDEX_ANALOG_LEFT]
      [RETRO_DEVICE_ID_ANALOG_Y]     = -lsy;
   analog_state[0][RETRO_DEVICE_INDEX_ANALOG_RIGHT]
      [RETRO_DEVICE_ID_ANALOG_X]     = rsx;
   analog_state[0][RETRO_DEVICE_INDEX_ANALOG_RIGHT]
      [RETRO_DEVICE_ID_ANALOG_Y]     = -rsy;
}
#endif

#ifdef HAVE_LIBNX
bool switch_joypad_set_rumble(unsigned pad,
      enum retro_rumble_effect type, uint16_t strength)
{
   HidVibrationDeviceHandle* handle;
   float amp;

   if (pad >= DEFAULT_MAX_PADS || !vibration_handles[pad])
      return false;

   amp  = (float)strength / 65535.0f;
   amp *= 0.5f; /* Max strength is too strong */

   if (type == RETRO_RUMBLE_STRONG)
   {
      vibration_values[pad][0].amp_low = amp;
      vibration_values[pad][1].amp_low = amp;
   }
   else
   {
      vibration_values[pad][0].amp_high = amp;
      vibration_values[pad][1].amp_high = amp;
   }

   handle = (pad == 0 && !padIsNpadActive(&pad_states[0], HidNpadIdType_No1))
      ? vibration_handleheld : vibration_handles[pad];
   return R_SUCCEEDED(hidSendVibrationValues(handle, vibration_values[pad], 2));
}
#endif

input_device_driver_t switch_joypad = {
   switch_joypad_init,
   switch_joypad_query_pad,
   switch_joypad_destroy,
   switch_joypad_button,
   switch_joypad_state,
   switch_joypad_get_buttons,
   switch_joypad_axis,
   switch_joypad_poll,
#ifdef HAVE_LIBNX
   switch_joypad_set_rumble,
#else
   NULL, /* set_rumble */
#endif
   NULL,
   switch_joypad_name,
   "switch"
};
