/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2002 by Felix Arends
 *
 * This program 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 Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include <math.h>
#include <stdlib.h>         /* EXIT_SUCCESS */
#include <stdio.h>

#include <3ds/types.h>
#include <3ds/services/apt.h>
#include <3ds/services/hid.h>
#include <3ds/services/mcuhwc.h>
#include <3ds/services/dsp.h>

#include "config.h"
#include "button.h"
#include "kernel.h"
#include "backlight.h"
#include "system.h"
#include "button-ctru.h"
#include "buttonmap.h"
#include "debug.h"
#include "powermgmt.h"
#include "storage.h"
#include "settings.h"
#include "sound.h"
#include "misc.h"

#include "touchscreen.h"
#include "audio.h"
#include "audio_thread.h"

static u8 old_slider_level = 0;
static int last_y, last_x;
static int slider_poll_counter = 0;

static enum {
    STATE_UNKNOWN = -1,
    STATE_UP = 0,
    STATE_DOWN = 1,
} last_touch_state = STATE_UNKNOWN;

static bool touch_enabled = true;

static double map_values(double n, double source_start, double source_end, double dest_start, double dest_end, int decimal_precision ) {
    double delta_start = source_end - source_start;
    double delta_end = dest_end - dest_start;
    if(delta_start == 0.0 || delta_end == 0.0) {
        return 1.0;
    }
    double scale  = delta_end / delta_start;
    double neg_start   = -1.0 * source_start;
    double offset = (neg_start * scale) + dest_start;
    double final_number = (n * scale) + offset;
    int calc_scale = (int) pow(10.0, decimal_precision);
    return (double) round(final_number * calc_scale) / calc_scale;
}

void update_sound_slider_level(void)
{
      /* Throttle the IPC call — only check every 16 reads (~every 250ms).
       * The slider is a physical control; users don't move it faster than
       * that, and each MCUHWC_GetSoundSliderLevel() is a round-trip IPC. */
      if (++slider_poll_counter < 16)
          return;
      slider_poll_counter = 0;

      /* update global volume based on sound slider level */
      u8 level;
      MCUHWC_GetSoundSliderLevel(&level);

      if (level != old_slider_level) {
          int volume = (int) map_values((double) level,
                                        0.0,             /* min slider volume */
                                        (double) 0x3f,   /* max slider value */
                                        (double) sound_min(SOUND_VOLUME),
                                        (double) sound_max(SOUND_VOLUME),
                                        0);
         global_status.volume = volume;
         setvol();
         old_slider_level = level;
      }
}

#define CPAD_DEADZONE 64

int button_read_device(int* data)
{
    int key = BUTTON_NONE;

    if (!aptMainLoop())
    {
        /* OS wants us to close (HOME → X, power-off, etc).
         * Trigger shutdown immediately instead of returning BUTTON_POWER,
         * which would need ~3s of repeat-counting before sys_poweroff()
         * fires — leaving the 3DS stuck on "waiting for software to close". */
        static bool shutting_down = false;
        if (!shutting_down)
        {
            shutting_down = true;
            sys_poweroff();
        }
        return BUTTON_NONE;
    }

    hidScanInput();
    u32 kDown = hidKeysDown();
    u32 kHeld = hidKeysHeld();

    /* Circle pad → directional buttons (alternative to D-pad) */
    circlePosition cpad;
    hidCircleRead(&cpad);
    if (cpad.dx > CPAD_DEADZONE)
        kHeld |= KEY_DRIGHT;
    else if (cpad.dx < -CPAD_DEADZONE)
        kHeld |= KEY_DLEFT;
    if (cpad.dy > CPAD_DEADZONE)
        kHeld |= KEY_DUP;
    else if (cpad.dy < -CPAD_DEADZONE)
        kHeld |= KEY_DDOWN;

    /* rockbox will handle button repeats */
    kDown |= kHeld;

    if (kDown & KEY_SELECT) {
        touchscreen_set_mode(touchscreen_get_mode() == TOUCHSCREEN_POINT ? TOUCHSCREEN_BUTTON : TOUCHSCREEN_POINT);
        DEBUGF("Touchscreen mode: %s\n", touchscreen_get_mode() == TOUCHSCREEN_POINT ? "TOUCHSCREEN_POINT" : "TOUCHSCREEN_BUTTON");
    }

    /* Shoulder button combos — global playback controls:
     * L + A → play/pause toggle
     * R + Left/Right → previous/next track
     * L + R → view current playlist/queue
     * These use audio_queue_post (non-blocking) so they work safely
     * from the button thread in any context. */
    bool l_held = (kHeld & KEY_L) != 0;
    bool r_held = (kHeld & KEY_R) != 0;
    bool both_shoulders = l_held && r_held;

    /* L+R triggers on the edge — when the second shoulder is pressed */
    if (both_shoulders && (kDown & (KEY_L | KEY_R)))
        key |= BUTTON_VIEWLIST;

    /* If both shoulders are held, A/Left/Right are consumed by VIEWLIST */
    if (!both_shoulders)
    {
        if (l_held && (kDown & KEY_A))
        {
            int status = audio_status();
            if (status & AUDIO_STATUS_PLAY)
            {
                if (status & AUDIO_STATUS_PAUSE)
                    audio_queue_post(Q_AUDIO_PAUSE, false);
                else
                    audio_queue_post(Q_AUDIO_PAUSE, true);
            }
            /* If not playing, jump to WPS / start playback */
            else
                key |= BUTTON_PLAYPAUSE;
        }
        else if (kDown & KEY_A)
            key |= BUTTON_SELECT;

        if (r_held && (kDown & KEY_DRIGHT))
            audio_queue_post(Q_AUDIO_SKIP, 1);
        else if (kDown & KEY_DRIGHT)
            key |= BUTTON_RIGHT;

        if (r_held && (kDown & KEY_DLEFT))
            audio_queue_post(Q_AUDIO_SKIP, -1);
        else if (kDown & KEY_DLEFT)
            key |= BUTTON_LEFT;
    }

    if (kDown & KEY_B) {
        key |= BUTTON_BACK;
    }
    if (kDown & KEY_X) {
        key |= BUTTON_MENU;
    }
    if (kDown & KEY_Y) {
        key |= BUTTON_USER;
    }
    if (kDown & KEY_START) {
        key |= BUTTON_POWER;
    }
    if (kDown & KEY_DUP) {
        key |= BUTTON_UP;
    }
    if (kDown & KEY_DDOWN) {
        key |= BUTTON_DOWN;
    }

    touchPosition touch;
    if (touch_enabled)
    {
        hidTouchRead(&touch);

        /* Generate UP and DOWN events */
        if (kDown & KEY_TOUCH) {
            last_touch_state = STATE_DOWN;
        }
        else {
            last_touch_state = STATE_UP;
        }

        last_x = touch.px;
        last_y = touch.py;

        int tkey = touchscreen_to_pixels(last_x, last_y, data);
        if (last_touch_state == STATE_DOWN) {
            key |= tkey;
        }
    }

    update_sound_slider_level();

    return key;
}

void button_init_device(void)
{
    hidInit();
}

#ifndef HAS_BUTTON_HOLD
void touchscreen_enable_device(bool en)
{
    touch_enabled = en;
}
#endif

bool headphones_inserted(void)
{
    bool is_inserted;
    DSP_GetHeadphoneStatus(&is_inserted);
    return is_inserted;
}
