/*  RetroArch - A frontend for libretro.
 *  Copyright (C) 2013-2014 - Jason Fetters
 *  Copyright (C) 2011-2017 - Daniel De Matteis
 *
 *  RetroArch is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with RetroArch.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include <string.h>

#include <string/stdstring.h>

#include "../input_driver.h"
#include "../../verbosity.h"

#include "joypad_connection.h"



/* we init the hid/vid to 0 because we need to do endian magic that we can't do during the declaration */
joypad_connection_entry_t pad_map[] = {
   { "Nintendo RVL-CNT-01",           0,     0,  &pad_connection_wii },
   { "Nintendo RVL-CNT-01-UC",        0,     0,  &pad_connection_wiiupro },
   { "Wireless Controller",           0,     0,  &pad_connection_ps4 },
   { "PLAYSTATION(R)3 Controller",    0,     0,  &pad_connection_ps3 },
   { "PLAYSTATION(R)3 Controller",    0,     0,  &pad_connection_ps3 },
   { "Generic SNES USB Controller",   0,     0,  &pad_connection_snesusb },
   { "Generic NES USB Controller",    0,     0,  &pad_connection_nesusb },
   { "Wii U GC Controller Adapter",   0,     0,  &pad_connection_wiiugca },
   { "PS2/PSX Controller Adapter",    0,     0,  &pad_connection_ps2adapter },
   { "PSX to PS3 Controller Adapter", 0,     0,  &pad_connection_psxadapter },
   { "Mayflash DolphinBar",           0,     0,  &pad_connection_wii },
   { "Retrode",                       0,     0,  &pad_connection_retrode },
   { "HORI mini wired PS4",           0,     0,  &pad_connection_ps4_hori_mini },
   { 0, 0}
};

static bool joypad_is_end_of_list(joypad_connection_t *pad)
{
  return pad
     && !pad->connected
     && !pad->iface
     &&  (pad->data == (void *)0xdeadbeef);
}

int pad_connection_find_vacant_pad(joypad_connection_t *joyconn)
{
  unsigned i;

  if (!joyconn)
    return -1;

  for (i = 0; !joypad_is_end_of_list(&joyconn[i]); i++)
  {
    if(!joyconn[i].connected)
      return i;
  }

  return -1;
}

static void set_end_of_list(joypad_connection_t *list, unsigned end)
{
  joypad_connection_t *entry = (joypad_connection_t *)&list[end];
  entry->connected           = false;
  entry->iface               = NULL;
  entry->data                = (void*)0xdeadbeef;
}


/**
 * Since the pad_connection_destroy() call needs to iterate through this
 * list, we allocate pads+1 entries and use the extra spot to store a
 * marker.
 */
joypad_connection_t *pad_connection_init(unsigned pads)
{
   unsigned i;
   joypad_connection_t *joyconn;

   if(pads > MAX_USERS)
   {
     RARCH_WARN("[joypad] invalid number of pads requested (%d), using default (%d)\n",
      pads, MAX_USERS);
     pads = MAX_USERS;
   }

   joyconn = (joypad_connection_t*)calloc(pads+1, sizeof(joypad_connection_t));

   if (!joyconn)
      return NULL;

   for (i = 0; i < pads; i++)
   {
      joypad_connection_t *conn = (joypad_connection_t*)&joyconn[i];

      conn->connected           = false;
      conn->iface               = NULL;
      conn->data                = NULL;
   }

   set_end_of_list(joyconn, pads);

   return joyconn;
}

void init_pad_map() {
   pad_map[0].vid         = VID_NINTENDO;
   pad_map[0].pid         = PID_NINTENDO_PRO;
   pad_map[1].vid         = VID_NINTENDO;
   pad_map[1].pid         = PID_NINTENDO_PRO;
   pad_map[2].vid         = VID_SONY;
   pad_map[2].pid         = PID_SONY_DS4;
   pad_map[3].vid         = VID_SONY;
   pad_map[3].pid         = PID_SONY_DS3;
   pad_map[4].vid         = VID_PS3_CLONE;
   pad_map[4].pid         = PID_DS3_CLONE;
   pad_map[5].vid         = VID_SNES_CLONE;
   pad_map[5].pid         = PID_SNES_CLONE;
   pad_map[6].vid         = VID_MICRONTEK;
   pad_map[6].pid         = PID_MICRONTEK_NES;
   pad_map[7].vid         = VID_NINTENDO;
   pad_map[7].pid         = PID_NINTENDO_GCA;
   pad_map[8].vid         = VID_PCS;
   pad_map[8].pid         = PID_PCS_PS2PSX;
   pad_map[9].vid         = VID_PCS;
   pad_map[9].pid         = PID_PCS_PSX2PS3;
   pad_map[10].vid        = 1406;
   pad_map[10].pid        = 774;
   pad_map[11].vid        = VID_RETRODE;
   pad_map[11].pid        = PID_RETRODE;
   pad_map[12].vid        = VID_HORI_1;
   pad_map[12].pid        = PID_HORI_MINI_WIRED_PS4;
}

joypad_connection_entry_t *find_connection_entry(int16_t vid, int16_t pid, const char *name) {
   unsigned i;
   const bool has_name = !string_is_empty(name);

   for(i = 0; pad_map[i].name != NULL; i++) {
      const char *name_match = has_name ? strstr(pad_map[i].name, name) : NULL;
      /* The Wii Pro Controller and Wii U pro controller have the same VID/PID, so we have to use the
         * descriptor string to differentiate them. */
      if(pad_map[i].vid == VID_NINTENDO && pad_map[i].pid == PID_NINTENDO_PRO)
      {
         if(!string_is_equal(pad_map[i].name, name))
            continue;
      }      

      if(name_match || (pad_map[i].vid == vid && pad_map[i].pid == pid)) {
         return &pad_map[i];
      }
   }

   return NULL;
}

int32_t pad_connection_pad_init(joypad_connection_t *joyconn,
   const char *name, uint16_t vid, uint16_t pid,
   void *data, hid_driver_t *driver)
{
   joypad_connection_entry_t *entry = NULL;
   joypad_connection_t *s = NULL;
   int pad = -1;

   if(pad_map[0].vid == 0) {
      init_pad_map();
   }

   entry = find_connection_entry(vid, pid, name); 
   pad = pad_connection_find_vacant_pad(joyconn);

   if (pad == -1)
      return -1;

   s = &joyconn[pad];

   if (s)
   {
      if(entry) {
         RARCH_DBG("Pad was matched to \"%s\". Setting up an interface.\n", entry->name);
         s->iface      = entry->iface;
         s->data       = data;
         s->connection = s->iface->init(data, pad, driver);
         s->connected  = true;
      } else {
         /* We failed to find a matching pad,
          * set up one without an interface */
         RARCH_DBG("Pad was not matched. Setting up without an interface.\n");
         s->iface     = NULL;
         s->data      = data;
         s->connected = true;
      }
   }

   return pad;
}

void pad_connection_pad_deinit(joypad_connection_t *joyconn, uint32_t pad)
{
   if (!joyconn || !joyconn->connected)
       return;

   if (joyconn->iface)
   {
      joyconn->iface->set_rumble(joyconn->connection, RETRO_RUMBLE_STRONG, 0);
      joyconn->iface->set_rumble(joyconn->connection, RETRO_RUMBLE_WEAK, 0);

      if (joyconn->iface->deinit)
         joyconn->iface->deinit(joyconn->connection);
   }

   joyconn->iface      = NULL;
   joyconn->connected  = false;
   joyconn->connection = NULL;
}

void pad_connection_packet(joypad_connection_t *joyconn, uint32_t pad,
      uint8_t* data, uint32_t length)
{
   if (!joyconn || !joyconn->connected)
       return;
   if (joyconn->iface && joyconn->connection && joyconn->iface->packet_handler)
      joyconn->iface->packet_handler(joyconn->connection, data, length);
}

void pad_connection_get_buttons(joypad_connection_t *joyconn,
      unsigned pad, input_bits_t *state)
{
   if (joyconn && joyconn->iface)
      joyconn->iface->get_buttons(joyconn->connection, state);
   else
      BIT256_CLEAR_ALL_PTR( state );
}

int16_t pad_connection_get_axis(joypad_connection_t *joyconn,
   unsigned idx, unsigned i)
{
   if (!joyconn || !joyconn->iface)
      return 0;
   return joyconn->iface->get_axis(joyconn->connection, i);
}

bool pad_connection_has_interface(joypad_connection_t *joyconn, unsigned pad)
{
   if (     joyconn && pad < MAX_USERS
         && joyconn[pad].connected
         && joyconn[pad].iface)
      return true;
   return false;
}

void pad_connection_destroy(joypad_connection_t *joyconn)
{
   unsigned i;

   for (i = 0; !joypad_is_end_of_list(&joyconn[i]); i ++)
     pad_connection_pad_deinit(&joyconn[i], i);

   free(joyconn);
}

bool pad_connection_rumble(joypad_connection_t *joyconn,
   unsigned pad, enum retro_rumble_effect effect, uint16_t strength)
{
   if (!joyconn->connected)
      return false;
   if (!joyconn->iface || !joyconn->iface->set_rumble)
      return false;

   joyconn->iface->set_rumble(joyconn->connection, effect, strength);
   return true;
}

const char* pad_connection_get_name(joypad_connection_t *joyconn, unsigned pad)
{
   if (!joyconn || !joyconn->iface || !joyconn->iface->get_name)
      return NULL;
   return joyconn->iface->get_name(joyconn->connection);
}