/*  RetroArch - A frontend for libretro.
 *  Copyright (C) 2013 - Jason Fetters
 * 
 *  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/>.
 */

#import "apple/common/RetroArch_Apple.h"
#import "views.h"

#include "apple/common/apple_input.h"
#include "apple/common/keycode.h"
#include "bluetooth/btdynamic.h"
#include "bluetooth/btpad.h"

static const void* const associated_userdata_key = &associated_userdata_key;
static const void* const associated_module_key = &associated_module_key;
static const void* const associated_setting_key = &associated_setting_key;

enum SettingTypes
{
   BooleanSetting, ButtonSetting, EnumerationSetting, FileListSetting,
   GroupSetting, AspectSetting, RangeSetting, CustomAction
};

@interface RASettingData : NSObject
{
   @public
   enum SettingTypes type;

   NSString* label;              // < The label displayed in the settings menu
   NSString* name;               // < The key name of the value in the config file
   NSString* value;              // < The current state of the setting

   uint32_t player;              // < The player that a ButtonSetting represents
   NSString* button_bind;        // < The Gamepad button binding string
   NSString* axis_bind;          // < The Gamepad axis binding string
   
   NSString* path;               // < The base path for FileListSettings
   NSArray* subValues;           // < The available options for EnumerationSettings and FileListSettings
   bool haveNoneOption;          // < Determines if a 'None' option is added to an Enumeration or FileList
   bool haveDescriptions;        // < Determines if subValues containts friendly descriptions for each value
   
   double rangeMin;              // < The mininum value of a range setting
   double rangeMax;              // < The maximum value of a range setting
   
   void (*changed)(RASettingData* action);
   void (*reload)(RASettingData* action, id userdata);
}

- (void)setValue:(NSString*)aValue;

@end

@implementation RASettingData
- (id)initWithType:(enum SettingTypes)aType label:(NSString*)aLabel name:(NSString*)aName
{
   type = aType;
   label = aLabel;
   name = aName;
   return self;
}

- (void)setValue:(NSString*)aValue;
{
   value = aValue;
   
   if (changed)
      changed(self);
}

- (uint32_t)enumerationCount
{
   return subValues.count / (haveDescriptions ? 2 : 1);
}

- (NSString*)valueForEnumerationIndex:(uint32_t)index
{
   return subValues[index * (haveDescriptions ? 2 : 1)];
}

- (NSString*)labelForEnumerationIndex:(uint32_t)index
{
   return subValues[index * (haveDescriptions ? 2 : 1) + (haveDescriptions ? 1 : 0)];
}

- (NSString*)labelForEnumerationValue
{
   const uint32_t count = self.enumerationCount;
   
   for (int i = 0; haveDescriptions && i < count; i ++)
   {
      if ([value isEqualToString:subValues[i * 2]])
         return subValues[i * 2 + 1];
   }
   
   return value;
}

@end

// Helper view definitions
@interface RAButtonGetter : NSObject<UIAlertViewDelegate>
- (id)initFromTable:(UITableView*)table;
- (void)runForSetting:(RASettingData*)setting;
@end

@interface RASettingEnumerationList : UITableViewController
- (id)initWithSetting:(RASettingData*)setting fromTable:(UITableView*)table;
@end


static RASettingData* boolean_setting(config_file_t* config, NSString* name, NSString* label, NSString* defaultValue)
{
   RASettingData* result = [[RASettingData alloc] initWithType:BooleanSetting label:label name:name];
   result->value = objc_get_value_from_config(config, name, defaultValue);
   return result;
}

static RASettingData* button_setting(config_file_t* config, uint32_t player, NSString* name, NSString* label, NSString* defaultValue)
{
   NSString* realname = player ? [NSString stringWithFormat:@"input_player%d_%@", player, name] : name;
   
   RASettingData* result = [[RASettingData alloc] initWithType:ButtonSetting label:label name:realname];
   result->player = player ? player - 1 : 0;
   result->value = objc_get_value_from_config(config, realname, defaultValue);
   result->button_bind = objc_get_value_from_config(config, [realname stringByAppendingString:@"_btn"], @"nul");
   result->axis_bind = objc_get_value_from_config(config, [realname stringByAppendingString:@"_axis"], @"nul");
   return result;
}

static RASettingData* group_setting(NSString* label, NSArray* settings)
{
   RASettingData* result = [[RASettingData alloc] initWithType:GroupSetting label:label name:nil];
   result->subValues = settings;
   return result;
}

static RASettingData* enumeration_setting(config_file_t* config, NSString* name, NSString* label, NSString* defaultValue, NSArray* values, bool haveDescriptions)
{
   RASettingData* result = [[RASettingData alloc] initWithType:EnumerationSetting label:label name:name];
   result->value = objc_get_value_from_config(config, name, defaultValue);
   result->subValues = values;
   result->haveDescriptions = haveDescriptions;
   return result;
}

static RASettingData* subpath_setting(config_file_t* config, NSString* name, NSString* label, NSString* defaultValue, NSString* path, NSString* extension)
{
   NSString* value = objc_get_value_from_config(config, name, defaultValue);
   value = [value stringByReplacingOccurrencesOfString:path withString:@""];

   NSArray* values = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:path error:nil];
   values = [values pathsMatchingExtensions:[NSArray arrayWithObject:extension]];

   RASettingData* result = [[RASettingData alloc] initWithType:FileListSetting label:label name:name];
   result->value = value;
   result->subValues = values;
   result->path = path;
   result->haveNoneOption = true;
   return result;
}

static RASettingData* range_setting(config_file_t* config, NSString* name, NSString* label, NSString* defaultValue, double minValue, double maxValue)
{
   RASettingData* result = [[RASettingData alloc] initWithType:RangeSetting label:label name:name];
   result->value = objc_get_value_from_config(config, name, defaultValue);
   result->rangeMin = minValue;
   result->rangeMax = maxValue;
   return result;
}

static RASettingData* aspect_setting(config_file_t* config, NSString* label)
{
   // Why does this need to be so difficult?

   RASettingData* result = [[RASettingData alloc] initWithType:AspectSetting label:label name:@""];
   result->subValues = [NSArray arrayWithObjects:@"Fill Screen", @"Game Aspect", @"Pixel Aspect", @"4:3", @"16:9", nil];

   bool videoForceAspect = true;
   bool videoAspectAuto = false;
   double videoAspect = -1.0;

   if (config)
   {
      config_get_bool(config, "video_force_aspect", &videoForceAspect);
      config_get_bool(config, "video_aspect_auto", &videoAspectAuto);
      config_get_double(config, "video_aspect_ratio", &videoAspect);
   }
   
   if (!videoForceAspect)
      result->value = @"Fill Screen";
   else if (videoAspect < 0.0)
      result->value = videoAspectAuto ? @"Game Aspect" : @"Pixel Aspect";
   else
      result->value = (videoAspect < 1.5) ? @"4:3" : @"16:9";
   
   return result;
}

static RASettingData* custom_action(NSString* action, NSString* value, id data, void (*reload_func)(RASettingData* action, id userdata))
{
   RASettingData* result = [[RASettingData alloc] initWithType:CustomAction label:action name:nil];
   result->value = value;
   result->reload = reload_func;
   
   if (data != nil)
      objc_setAssociatedObject(result, associated_userdata_key, data, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
   
   return result;
}

// This adds a change notify function to a setting and returns it; done this way so it can be used in NSArray
// init lists.
static RASettingData* change_notify(RASettingData* setting, void (*change_func)(RASettingData* setting))
{
   setting->changed = change_func;
   return setting;
}

static NSArray* build_input_port_group(config_file_t* config, uint32_t player)
{
   // Only player 1 should have default key bindings
   #define DEFKEY(val) (player == 1) ? val : @"nul"

   return [NSArray arrayWithObjects:
            [NSArray arrayWithObjects:[NSString stringWithFormat:@"Player %d", player],
               button_setting(config, player, @"up", @"Up", DEFKEY(@"up")),
               button_setting(config, player, @"down", @"Down", DEFKEY(@"down")),
               button_setting(config, player, @"left", @"Left", DEFKEY(@"left")),
               button_setting(config, player, @"right", @"Right", DEFKEY(@"right")),

               button_setting(config, player, @"start", @"Start", DEFKEY(@"enter")),
               button_setting(config, player, @"select", @"Select", DEFKEY(@"rshift")),

               button_setting(config, player, @"b", @"B", DEFKEY(@"z")),
               button_setting(config, player, @"a", @"A", DEFKEY(@"x")),
               button_setting(config, player, @"x", @"X", DEFKEY(@"s")),
               button_setting(config, player, @"y", @"Y", DEFKEY(@"a")),

               button_setting(config, player, @"l", @"L", DEFKEY(@"q")),
               button_setting(config, player, @"r", @"R", DEFKEY(@"w")),
               button_setting(config, player, @"l2", @"L2", @"nul"),
               button_setting(config, player, @"r2", @"R2", @"nul"),
               button_setting(config, player, @"l3", @"L3", @"nul"),
               button_setting(config, player, @"r3", @"R3", @"nul"),

               button_setting(config, player, @"l_y_minus", @"Left Stick Up", @"nul"),
               button_setting(config, player, @"l_y_plus", @"Left Stick Down", @"nul"),
               button_setting(config, player, @"l_x_minus", @"Left Stick Left", @"nul"),
               button_setting(config, player, @"l_x_plus", @"Left Stick Right", @"nul"),
               button_setting(config, player, @"r_y_minus", @"Right Stick Up", @"nul"),
               button_setting(config, player, @"r_y_plus", @"Right Stick Down", @"nul"),
               button_setting(config, player, @"r_x_minus", @"Right Stick Left", @"nul"),
               button_setting(config, player, @"r_x_plus", @"Right Stick Right", @"nul"),
               nil],
            nil];
}

@implementation RASettingsList
{
   RAModuleInfo* _module;
   NSString* _configPath;
   bool _cancelSave; // Set to prevent dealloc from writing to disk
}

+ (void)refreshModuleConfig:(RAModuleInfo*)module;
{
   (void)[[RASettingsList alloc] initWithModule:module];
}

- (id)initWithModule:(RAModuleInfo*)module
{
   _module = module;
   _configPath = _module ? _module.configFile : apple_platform.globalConfigFile;

   config_file_t* config = config_file_new([_configPath UTF8String]);

   NSString* overlay_path = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/overlays/"];
   NSString* shader_path = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/shaders_glsl/"];

   NSArray* settings = [NSArray arrayWithObjects:
      [NSArray arrayWithObjects:@"Core",
         custom_action(@"Core Info", nil, nil, 0),
         _module.hasCustomConfig ? custom_action(@"Delete Custom Config", nil, nil, 0) : nil,
         nil],

      [NSArray arrayWithObjects:@"Video",
         boolean_setting(config, @"video_smooth", @"Bilinear filtering", @"true"),
         boolean_setting(config, @"video_crop_overscan", @"Crop Overscan", @"true"),
         boolean_setting(config, @"video_scale_integer", @"Integer Scaling", @"false"),
         aspect_setting(config, @"Aspect Ratio"),
         nil],
         
      [NSArray arrayWithObjects:@"GPU Shader",         
         boolean_setting(config, @"video_shader_enable", @"Enable Shader", @"false"),
         subpath_setting(config, @"video_shader", @"Shader", @"", shader_path, @"glsl"),
         nil],

      [NSArray arrayWithObjects:@"Audio",
         boolean_setting(config, @"audio_enable", @"Enable Output", @"true"),
         boolean_setting(config, @"audio_sync", @"Sync on Audio", @"true"),
         boolean_setting(config, @"audio_rate_control", @"Rate Control", @"true"),
         nil],

      [NSArray arrayWithObjects:@"Input",
         subpath_setting(config, @"input_overlay", @"Input Overlay", @"", overlay_path, @"cfg"),
         range_setting(config, @"input_overlay_opacity", @"Overlay Opacity", @"1.0", 0.0, 1.0),
         group_setting(@"System Keys", [NSArray arrayWithObjects:
            // TODO: Many of these strings will be cut off on an iPhone
            [NSArray arrayWithObjects:@"System Keys",
               button_setting(config, 0, @"input_menu_toggle", @"Show RGUI", @"f1"),
               button_setting(config, 0, @"input_disk_eject_toggle", @"Insert/Eject Disk", @"nul"),
               button_setting(config, 0, @"input_disk_next", @"Cycle Disks", @"nul"),
               button_setting(config, 0, @"input_save_state", @"Save State", @"f2"),
               button_setting(config, 0, @"input_load_state", @"Load State", @"f4"),
               button_setting(config, 0, @"input_state_slot_increase", @"Next State Slot", @"f7"),
               button_setting(config, 0, @"input_state_slot_decrease", @"Previous State Slot", @"f6"),
               button_setting(config, 0, @"input_toggle_fast_forward", @"Toggle Fast Forward", @"space"),
               button_setting(config, 0, @"input_hold_fast_forward", @"Hold Fast Forward", @"l"),
               button_setting(config, 0, @"input_rewind", @"Rewind", @"r"),
               button_setting(config, 0, @"input_slowmotion", @"Slow Motion", @"e"),
               button_setting(config, 0, @"input_reset", @"Reset", @"h"),
               button_setting(config, 0, @"input_exit_emulator", @"Close Game", @"escape"),
               button_setting(config, 0, @"input_enable_hotkey", @"Hotkey Enable (Always on if not set)", @"nul"),
               nil],
            nil]),
         group_setting(@"Player 1 Keys", build_input_port_group(config, 1)),
         group_setting(@"Player 2 Keys", build_input_port_group(config, 2)),
         group_setting(@"Player 3 Keys", build_input_port_group(config, 3)),
         group_setting(@"Player 4 Keys", build_input_port_group(config, 4)),
         nil],
      
      [NSArray arrayWithObjects:@"Save States",
         boolean_setting(config, @"rewind_enable", @"Enable Rewinding", @"false"),
         boolean_setting(config, @"block_sram_overwrite", @"Disable SRAM on Load", @"false"),
         boolean_setting(config, @"savestate_auto_save", @"Auto Save on Exit", @"false"),
         boolean_setting(config, @"savestate_auto_load", @"Auto Load on Startup", @"true"),
         nil],
      nil
   ];

   if (config)
      config_file_free(config);

   self = [super initWithSettings:settings title:_module ? _module.description : @"Global Core Config"];
   return self;
}

- (void)dealloc
{
   if (!_cancelSave)
   {
      config_file_t* config = config_file_new([_configPath UTF8String]);
    
      if (!config)
         config = config_file_new(0);

      if (config)
      {
         config_set_string(config, "system_directory", [[RetroArch_iOS get].systemDirectory UTF8String]);
         config_set_string(config, "savefile_directory", [[RetroArch_iOS get].systemDirectory UTF8String]);
         config_set_string(config, "savestate_directory", [[RetroArch_iOS get].systemDirectory UTF8String]);
         [self writeSettings:nil toConfig:config];
         config_file_write(config, [_configPath UTF8String]);
         config_file_free(config);
      }

      apple_refresh_config();
   }
}

- (void)handleCustomAction:(RASettingData*)setting
{
   if ([@"Core Info" isEqualToString:setting->label])
      [self.navigationController pushViewController:[[RAModuleInfoList alloc] initWithModuleInfo:_module] animated:YES];
   else if([@"Delete Custom Config" isEqualToString:setting->label])
   {
      [_module deleteCustomConfig];
      _cancelSave = true;
      [self.navigationController popViewControllerAnimated:YES];
   }
}

@end

#pragma mark System Settings

static void reload_core_config_state(RASettingData* action, RAModuleInfo* userdata)
{
   [action setValue:userdata.hasCustomConfig ? @"[Custom]" : @"[Global]"];
}

static void bluetooth_option_changed(RASettingData* setting)
{
   ios_set_bluetooth_mode(setting->value);
}

@implementation RASystemSettingsList
- (id)init
{
   config_file_t* config = config_file_new([[RetroArch_iOS get].systemConfigPath UTF8String]);
   
   NSMutableArray* modules = [NSMutableArray array];
   [modules addObject:@"Cores"];
   [modules addObject:custom_action(@"Global Core Config", nil, nil, 0)];

   NSArray* moduleList = apple_get_modules();
   for (RAModuleInfo* i in moduleList)
   {
      [modules addObject:custom_action(i.description, nil, i, reload_core_config_state)];
   }


   NSArray* bluetoothOptions = nil;
   
   if (!is_ios_7() && btstack_try_load())
      bluetoothOptions = @[@"keyboard", @"Keyboard", @"icade", @"iCade Device", @"btstack", @"WiiMote/SixAxis (BTstack)"];
   else if (!is_ios_7())
      bluetoothOptions = @[@"keyboard", @"Keyboard", @"icade", @"iCade Device"];
   else // if (is_ios_7())
      bluetoothOptions = @[@"none", @"None", @"icade", @"iCade Device"];

   NSArray* settings = [NSArray arrayWithObjects:
      [NSArray arrayWithObjects:@"Frontend",
         custom_action(@"Diagnostic Log", nil, nil, 0),
         boolean_setting(config, @"ios_tv_mode", @"TV Mode", @"false"),
         nil],
      [NSArray arrayWithObjects:@"Bluetooth",
         change_notify(enumeration_setting(config, @"ios_btmode", @"Mode", @"keyboard", bluetoothOptions, true), bluetooth_option_changed),
         nil],
      [NSArray arrayWithObjects:@"Orientations",
         boolean_setting(config, @"ios_allow_portrait", @"Portrait", @"true"),
         boolean_setting(config, @"ios_allow_portrait_upside_down", @"Portrait Upside Down", @"true"),
         boolean_setting(config, @"ios_allow_landscape_left", @"Landscape Left", @"true"),
         boolean_setting(config, @"ios_allow_landscape_right", @"Landscape Right", @"true"),
         nil],
      modules,
      nil
   ];

   if (config)
      config_file_free(config);

   self = [super initWithSettings:settings title:@"RetroArch Settings"];
   return self;
}

- (void)viewDidAppear:(BOOL)animated
{
   [self.tableView reloadData];
   [super viewDidAppear:animated];
}

- (void)dealloc
{
   config_file_t* config = config_file_new([[RetroArch_iOS get].systemConfigPath UTF8String]);
   
    if (!config)
        config = config_file_new(0);
   
   if (config)
   {
      [self writeSettings:nil toConfig:config];
      config_file_write(config, [[RetroArch_iOS get].systemConfigPath UTF8String]);
      config_file_free(config);
   }
   
   [[RetroArch_iOS get] refreshSystemConfig];
}

- (void)handleCustomAction:(RASettingData*)setting
{
   if ([@"Diagnostic Log" isEqualToString:setting->label])
      [self.navigationController pushViewController:[RALogView new] animated:YES];
   else if ([@"Enable BTstack" isEqualToString:setting->label])
      btstack_set_poweron([setting->value isEqualToString:@"true"]);
   else if([@"Global Core Config" isEqualToString:setting->label])
      [self.navigationController pushViewController:[[RASettingsList alloc] initWithModule:nil] animated:YES];
   else
   {
      RAModuleInfo* data = (RAModuleInfo*)objc_getAssociatedObject(setting, associated_userdata_key);
      if (data)
      {
         if (!data.hasCustomConfig)
         {
            UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"RetroArch"
                                                            message:@"No custom configuration for this core exists, "
                                                                     "would you like to create one?"
                                                           delegate:self
                                                  cancelButtonTitle:@"No"
                                                  otherButtonTitles:@"Yes", nil];
            objc_setAssociatedObject(alert, associated_module_key, data, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            [alert show];
         }
         else
            [self.navigationController pushViewController:[[RASettingsList alloc] initWithModule:data] animated:YES];
      }
   }
}

- (void)alertView:(UIAlertView*)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex
{
   RAModuleInfo* data = (RAModuleInfo*)objc_getAssociatedObject(alertView, associated_module_key);

   if (data)
   {
      if (buttonIndex == alertView.firstOtherButtonIndex)
      {
         [data createCustomConfig];
         [self.tableView reloadData];
      }

      [self.navigationController pushViewController:[[RASettingsList alloc] initWithModule:data] animated:YES];
   }

}

@end

@implementation RASettingsSubList
{
   RAButtonGetter* _binder;
}

- (id)initWithSettings:(NSMutableArray*)values title:(NSString*)title
{
   self = [super initWithStyle:UITableViewStyleGrouped];
   [self setTitle:title];
   
   self.sections = values;
   _binder = [[RAButtonGetter alloc] initFromTable:self.tableView];
   return self;
}

- (bool)isSettingsView
{
   return true;
}

- (void)handleCustomAction:(RASettingData*)setting
{

}

- (void)writeSettings:(NSArray*)settingList toConfig:(config_file_t*)config
{
   if (!config)
      return;

   NSArray* list = settingList ? settingList : self.sections;

   for (int i = 0; i < [list count]; i++)
   {
      NSArray* group = [list objectAtIndex:i];
   
      for (int j = 1; j < [group count]; j ++)
      {
         RASettingData* setting = [group objectAtIndex:j];
         
         switch (setting->type)
         {         
            case GroupSetting:
               [self writeSettings:setting->subValues toConfig:config];
               break;
               
            case FileListSetting:
               if ([setting->value length] > 0)
                  config_set_string(config, [setting->name UTF8String], [[setting->path stringByAppendingPathComponent:setting->value] UTF8String]);
               else
                  config_set_string(config, [setting->name UTF8String], "");
               break;

            case ButtonSetting:
               if (setting->value)
                  config_set_string(config, [setting->name UTF8String], [setting->value UTF8String]);
               if (setting->button_bind)
                  config_set_string(config, [[setting->name stringByAppendingString:@"_btn"] UTF8String], [setting->button_bind UTF8String]);
               if (setting->axis_bind)
                  config_set_string(config, [[setting->name stringByAppendingString:@"_axis"] UTF8String], [setting->axis_bind UTF8String]);
               break;

            case AspectSetting:
               config_set_string(config, "video_force_aspect", [@"Fill Screen" isEqualToString:setting->value] ? "false" : "true");
               config_set_string(config, "video_aspect_ratio_auto", [@"Game Aspect" isEqualToString:setting->value] ? "true" : "false");
               config_set_string(config, "video_aspect_ratio", "-1.0");
               if([@"4:3" isEqualToString:setting->value])
                  config_set_string(config, "video_aspect_ratio", "1.33333333");
               else if([@"16:9" isEqualToString:setting->value])
                  config_set_string(config, "video_aspect_ratio", "1.77777777");
               break;

            case CustomAction:
               break;

            default:
               config_set_string(config, [setting->name UTF8String], [setting->value UTF8String]);
               break;
         }
      }
   }
}

- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
   RASettingData* setting = (RASettingData*)[self itemForIndexPath:indexPath];
   
   switch (setting->type)
   {
      case EnumerationSetting:
      case FileListSetting:
      case AspectSetting:
         [self.navigationController pushViewController:[[RASettingEnumerationList alloc] initWithSetting:setting fromTable:(UITableView*)self.view] animated:YES];
         break;
         
      case ButtonSetting:
         [_binder runForSetting:setting];
         break;
         
      case GroupSetting:
         [self.navigationController pushViewController:[[RASettingsSubList alloc] initWithSettings:setting->subValues title:setting->label] animated:YES];
         break;
         
      default:
         break;
   }
   
   [self handleCustomAction:setting];
}

- (void)handleBooleanSwitch:(UISwitch*)swt
{
   RASettingData* setting = objc_getAssociatedObject(swt, associated_setting_key);
   [setting setValue:swt.on ? @"true" : @"false"];
   
   [self handleCustomAction:setting];
}

- (void)handleSlider:(UISlider*)sld
{
   RASettingData* setting = objc_getAssociatedObject(sld, associated_setting_key);
   [setting setValue:[NSString stringWithFormat:@"%f", sld.value]];

   [self handleCustomAction:setting];
}

- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
   RASettingData* setting = (RASettingData*)[self itemForIndexPath:indexPath];
  
   UITableViewCell* cell = nil;

   switch (setting->type)
   {
      case BooleanSetting:
      {
         static NSString* const cell_id = @"boolean";
         cell = [self.tableView dequeueReusableCellWithIdentifier:cell_id];

         if (cell == nil)
         {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cell_id];
         
            UISwitch* accessory = [[UISwitch alloc] init];
            [accessory addTarget:self action:@selector(handleBooleanSwitch:) forControlEvents:UIControlEventValueChanged];
            cell.accessoryView = accessory;
            
            [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
         }
      
         cell.textLabel.text = setting->label;
      
         UISwitch* swt = (UISwitch*)cell.accessoryView;
         swt.on = [setting->value isEqualToString:@"true"];
         objc_setAssociatedObject(swt, associated_setting_key, setting, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
         
         return cell;
      }

      case RangeSetting:
      {
         static NSString* const cell_id = @"range";
         cell = [self.tableView dequeueReusableCellWithIdentifier:cell_id];
         
         if (cell == nil)
         {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cell_id];

            UISlider* accessory = [UISlider new];
            [accessory addTarget:self action:@selector(handleSlider:) forControlEvents:UIControlEventValueChanged];
            accessory.continuous = NO;
            cell.accessoryView = accessory;

            [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
         }
         
         cell.textLabel.text = setting->label;
         
         UISlider* sld = (UISlider*)cell.accessoryView;
         sld.minimumValue = setting->rangeMin;
         sld.maximumValue = setting->rangeMax;
         sld.value = [setting->value doubleValue];
         objc_setAssociatedObject(sld, associated_setting_key, setting, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
         
         return cell;
      }
      break;
         
      default:
      {
         static NSString* const cell_id = @"default";
         cell = [self.tableView dequeueReusableCellWithIdentifier:cell_id];
         
         if (!cell)
         {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cell_id];
            cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
         }

         if (setting->reload)
            setting->reload(setting, objc_getAssociatedObject(setting, associated_userdata_key));
  
         cell.textLabel.text = setting->label;
   
         if (setting->type == ButtonSetting)
            cell.detailTextLabel.text = [NSString stringWithFormat:@"[KB:%@] [JS:%@] [AX:%@]",
               [setting->value length] ? setting->value : @"nul",
               [setting->button_bind length] ? setting->button_bind : @"nul",
               [setting->axis_bind length] ? setting->axis_bind : @"nul"];
         else if(setting->type == EnumerationSetting)
            cell.detailTextLabel.text = setting.labelForEnumerationValue;
         else
            cell.detailTextLabel.text = setting->value;

         return cell;
      }
   }
}

@end

@implementation RASettingEnumerationList
{
   RASettingData* _value;
   UITableView* _view;
   unsigned _mainSection;
};

- (id)initWithSetting:(RASettingData*)setting fromTable:(UITableView*)table
{
   self = [super initWithStyle:UITableViewStyleGrouped];
   
   _value = setting;
   _view = table;
   _mainSection = _value->haveNoneOption ? 1 : 0;
   
   [self setTitle: _value->label];
   return self;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView
{
   return _value->haveNoneOption ? 2 : 1;
}

- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
   return (section == _mainSection) ? _value.enumerationCount : 1;
}

- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
   static NSString* const cell_id = @"option";

   UITableViewCell* cell = [self.tableView dequeueReusableCellWithIdentifier:cell_id];
   cell = cell ? cell : [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cell_id];

   if (indexPath.section == _mainSection)
      cell.textLabel.text = [_value labelForEnumerationIndex:indexPath.row];
   else
      cell.textLabel.text = @"None";

   return cell;
}

- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
   [_value setValue: (indexPath.section == _mainSection) ? [_value valueForEnumerationIndex:indexPath.row] : @""];
   
   [_view reloadData];
   [self.navigationController popViewControllerAnimated:YES];
}

@end

@implementation RAButtonGetter
{
   RASettingData* _value;
   UIAlertView* _alert;
   UITableView* _view;
   bool _finished;
   NSTimer* _btTimer;
}

- (id)initFromTable:(UITableView*)table
{
   self = [super init];

   _view = table;

   _alert = [[UIAlertView alloc] initWithTitle:@"RetroArch"
                                 message:@""
                                 delegate:self
                                 cancelButtonTitle:@"Cancel"
                                 otherButtonTitles:@"Clear Keyboard", @"Clear Joystick", @"Clear Axis", nil];

   if (is_ios_7())
      _alert.alertViewStyle = UIAlertViewStylePlainTextInput;

   return self;
}

- (void)runForSetting:(RASettingData*)setting
{
   apple_input_reset_icade_buttons();

   _value = setting;

   _alert.message = _value->name;
   [_alert show];
   
   _btTimer = [NSTimer scheduledTimerWithTimeInterval:.05f target:self selector:@selector(checkInput) userInfo:nil repeats:YES];
   
   _finished = false;
}

- (void)finish
{
   if (!_finished)
   {
      _finished = true;
   
      apple_input_reset_icade_buttons();
   
      [_btTimer invalidate];
      _btTimer = nil;

      [_alert dismissWithClickedButtonIndex:_alert.cancelButtonIndex animated:YES];
      [_view reloadData];
   }
}

- (void)alertView:(UIAlertView*)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex
{
   if (buttonIndex == _alert.firstOtherButtonIndex)
      _value->value = @"nul";
   else if(buttonIndex == _alert.firstOtherButtonIndex + 1)
      _value->button_bind = @"nul";
   else if(buttonIndex == _alert.firstOtherButtonIndex + 2)
      _value->axis_bind = @"nul";

   [self finish];
}

- (void)checkInput
{
   int32_t value = 0;

   if ((value = apple_input_find_any_key()))
      _value->value = @(apple_keycode_hidusage_to_name(value));
   else if ((value = apple_input_find_any_button(0)) >= 0)
      _value->button_bind = [NSString stringWithFormat:@"%d", value];
   else if ((value = apple_input_find_any_axis(0)))
      _value->axis_bind = [NSString stringWithFormat:@"%s%d", (value > 0) ? "+" : "-", value - 1];
   else
      return;
   
   [self finish];
}

@end

