/* Copyright (c) 2012 Research In Motion Limited.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "RetroArch-Cascades.h"
#include "general.h"
#include "conf/config_file.h"
#include "file.h"
#include "core_info.h"

#ifdef HAVE_RGUI
#include "frontend/menu/rgui.h"
#endif

#include "../../frontend_qnx.h"

#include <bb/cascades/AbsoluteLayoutProperties>
#include <bb/cascades/ForeignWindowControl>
#include <bb/cascades/AbstractPane>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/Window>
#include <bb/cascades/pickers/FilePicker>
#include <bb/data/JsonDataAccess>
#include <bb/device/HardwareInfo>
#include <bb/cascades/ListView>


#include <screen/screen.h>
#include <bps/screen.h>
#include <bps/navigator.h>
#include <bps/bps.h>

#include <math.h>
#include <dirent.h>
#include <bb/cascades/DropDown>

using namespace bb::cascades;
using namespace bb::data;
using namespace bb::device;

extern screen_window_t screen_win;
extern screen_context_t screen_ctx;

RetroArch::RetroArch()
{
   qmlRegisterType<bb::cascades::pickers::FilePicker>("bb.cascades.pickers", 1, 0, "FilePicker");
   qmlRegisterUncreatableType<bb::cascades::pickers::FileType>("bb.cascades.pickers", 1, 0, "FileType", "");

   // Create channel to signal threads on
   chid = ChannelCreate(0);
   coid = ConnectAttach(0, 0, chid, _NTO_SIDE_CHANNEL, 0);

   bool res = connect(
         OrientationSupport::instance(), SIGNAL(rotationCompleted()),
         this, SLOT(onRotationCompleted()));

   rarch_main_clear_state();
   strlcpy(g_extern.config_path, "app/native/retroarch.cfg", sizeof(g_extern.config_path));
   config_load();

   strlcpy(g_settings.libretro, "app/native/lib", sizeof(g_settings.libretro));
   coreSelectedIndex = -1;

   //Stop config overwritting values
   g_extern.block_config_read = true;

   QmlDocument *qml = QmlDocument::create("asset:///main.qml");

   if (!qml->hasErrors())
   {
      qml->setContextProperty("RetroArch", this);

      AbstractPane *mAppPane = qml->createRootObject<AbstractPane>();

      if (mAppPane)
      {
         //Get core DropDown reference to populate it in C++
         coreSelection = mAppPane->findChild<DropDown*>("dropdown_core");
         connect(coreSelection, SIGNAL(selectedValueChanged(QVariant)), this, SLOT(onCoreSelected(QVariant)));
         core_info_list = core_info_list_new(g_settings.libretro);
         populateCores(core_info_list);

         Application::instance()->setScene(mAppPane);

         screen_create_context(&screen_ctx, 0);
         input_qnx.init();
         buttonMap = new ButtonMap(screen_ctx, (const char*)Application::instance()->mainWindow()->groupId().toAscii().constData(), coid);
         qml->setContextProperty("ButtonMap", buttonMap);

         deviceSelection = mAppPane->findChild<DropDown*>("dropdown_devices");
         buttonMap->deviceSelection = deviceSelection;
         findDevices();

         //Setup the datamodel for button mapping.
         mAppPane->findChild<ListView*>("buttonMapList")->setDataModel(buttonMap->buttonDataModel);

         // Start the thread in which we render to the custom window.
         start();
      }
   }
}

RetroArch::~RetroArch()
{
   core_info_list_free(core_info_list);
}

void RetroArch::aboutToQuit()
{
   recv_msg msg;

   msg.code = RETROARCH_EXIT;

   MsgSend(coid, (void*)&msg, sizeof(msg), (void*)NULL, 0);

   wait();
}

void RetroArch::run()
{
   int rcvid = -1;
   recv_msg msg;

   bps_initialize();

   if (screen_request_events(screen_ctx) != BPS_SUCCESS)
   {
      RARCH_ERR("screen_request_events failed.\n");
   }

   if (navigator_request_events(0) != BPS_SUCCESS)
   {
      RARCH_ERR("navigator_request_events failed.\n");
   }

   if (navigator_rotation_lock(false) != BPS_SUCCESS)
   {
      RARCH_ERR("navigator_location_lock failed.\n");
   }

   while (true)
   {
      rcvid = MsgReceive(chid, &msg, sizeof(msg), 0);

      if (rcvid > 0)
      {
         switch (msg.code)
         {
         case RETROARCH_START_REQUESTED:
         {
            MsgReply(rcvid,0,NULL,0);

            if (screen_create_window_type(&screen_win, screen_ctx, SCREEN_CHILD_WINDOW) != BPS_SUCCESS)
            {
               RARCH_ERR("Screen create window failed.\n");
            }
            if (screen_join_window_group(screen_win, (const char*)Application::instance()->mainWindow()->groupId().toAscii().constData()) != BPS_SUCCESS)
            {
               RARCH_ERR("Screen join window group failed.\n");
            }

            char *win_id = "RetroArch_Emulator_Window";
            screen_set_window_property_cv(screen_win, SCREEN_PROPERTY_ID_STRING, strlen(win_id), win_id);

            int z = 10;
            if (screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_ZORDER, &z) != 0) {
               return;
            }

            initRASettings();

            rarch_main(0, NULL);
            Application::instance()->exit();
            break;
         }
         //The class should probably be it's own QThread, simplify things
         case RETROARCH_BUTTON_MAP:
            MsgReply(rcvid, buttonMap->mapNextButtonPressed(), NULL, 0);
            break;
         case RETROARCH_EXIT:
            MsgReply(rcvid,0,NULL,0);
            goto exit;
         default:
            break;
         }
      }
   }
exit:
   return;
}


/*
 * Properties
 */
QString RetroArch::getRom()
{
   return rom;
}

void RetroArch::setRom(QString rom)
{
   this->rom = rom;
}

QString RetroArch::getCore()
{
   return core;
}

void RetroArch::setCore(QString core)
{
   this->core = core;
}

QString RetroArch::getRomExtensions()
{
   return romExtensions;
}

/*
 * Slots
 */
void RetroArch::onRotationCompleted()
{
   if (OrientationSupport::instance()->orientation() == UIOrientation::Landscape)
   {
      if (state == RETROARCH_START_REQUESTED)
      {
         startEmulator();
      }
   }
}

void RetroArch::onCoreSelected(QVariant value)
{
   coreSelectedIndex = value.toInt();

   core.clear();
   core.append(core_info_list->list[coreSelectedIndex].path);
   emit coreChanged(core);

   romExtensions = QString("*.%1").arg(core_info_list->list[coreSelectedIndex].supported_extensions);
   romExtensions.replace("|", "|*.");
   emit romExtensionsChanged(romExtensions);

   qDebug() << "Core Selected: " << core;
   qDebug() << "Supported Extensions: " << romExtensions;
}

/*
 * Functions
 */
void RetroArch::startEmulator()
{
   state = RETROARCH_START_REQUESTED;

   if (OrientationSupport::instance()->orientation() == UIOrientation::Portrait &&
       OrientationSupport::instance()->supportedDisplayOrientation() != SupportedDisplayOrientation::DeviceNorth)
   {
      OrientationSupport::instance()->setSupportedDisplayOrientation(SupportedDisplayOrientation::DisplayLandscape);
   }
   else
   {
      recv_msg msg;
      msg.code = RETROARCH_START_REQUESTED;

      MsgSend(coid, (void*)&msg, sizeof(msg), (void*)NULL, 0);

      state = RETROARCH_RUNNING;
   }
}

void RetroArch::populateCores(core_info_list_t * info)
{
   int i;
   Option *tmp;

   //Populate DropDown
   for (i = 0; i < info->count; ++i)
   {
      qDebug() << info->list[i].display_name;

      tmp = Option::create().text(QString(info->list[i].display_name))
                            .value(i);

      coreSelection->add(tmp);
   }
}

void RetroArch::findDevices()
{
   //Find all connected devices.
   Option *tmp;

   deviceSelection->removeAll();

   //Populate DropDown
   for (int i = 0; i < pads_connected; ++i)
   {
      tmp = Option::create().text(devices[i].device_name)
                            .value(i);

      deviceSelection->add(tmp);

      //QML shows player 1 by default, so set dropdown to their controller.
      if(devices[i].port == 0 || devices[i].device == DEVICE_KEYPAD)
      {
         deviceSelection->setSelectedIndex(i);
      }
   }
}

extern "C" void discoverControllers();
void RetroArch::discoverController(int player)
{
   //TODO: Check device, gamepad/keyboard and return accordingly.
   discoverControllers();
   findDevices();
   buttonMap->refreshButtonMap(player);
   return;
}

void RetroArch::initRASettings()
{
   strlcpy(g_settings.libretro,(char *)core.toAscii().constData(), sizeof(g_settings.libretro));
   strlcpy(g_extern.fullpath, (char *)rom.toAscii().constData(), sizeof(g_extern.fullpath));

   HardwareInfo *hwInfo = new HardwareInfo();

   //If Physical keyboard or a device mapped to player 1, hide overlay
   //TODO: Should there be a minimized/quick settings only overlay?
   if(hwInfo->isPhysicalKeyboardDevice() || port_device[0])
      *g_settings.input.overlay = '\0';
}
