/**
 * @file dsp.h
 * @brief DSP Service to access the DSP processor commands (sound)
 *
 * The DSP has access to the Linear memory region, and to the DSP memory region if allowed in the exheader.
 */
#pragma once
#include <3ds/types.h>

/// DSP interrupt types.
typedef enum
{
	DSP_INTERRUPT_PIPE = 2 ///< Pipe interrupt.
} DSP_InterruptType;

/// DSP hook types.
typedef enum
{
	DSPHOOK_ONSLEEP  = 0, ///< DSP is going to sleep.
	DSPHOOK_ONWAKEUP = 1, ///< DSP is waking up.
	DSPHOOK_ONCANCEL = 2, ///< DSP was sleeping and the app was cancelled.
} DSP_HookType;

/// DSP hook function.
typedef void (* dspHookFn)(DSP_HookType hook);

/// DSP hook cookie.
typedef struct tag_dspHookCookie
{
	struct tag_dspHookCookie* next; ///< Next cookie.
	dspHookFn callback;             ///< Hook callback.
} dspHookCookie;

/**
 * @brief Initializes the dsp service.
 *
 * Call this before calling any DSP_* function.
 * @note This will also unload any previously loaded DSP binary.
 *       It is done this way since you have to provide your binary when the 3DS leaves sleep mode anyway.
 */
Result dspInit(void);

/**
 * @brief Closes the dsp service.
 * @note This will also unload the DSP binary.
 */
void dspExit(void);

/// Returns true if a component is loaded, false otherwise.
bool dspIsComponentLoaded(void);

/**
 * @brief Sets up a DSP status hook.
 * @param cookie Hook cookie to use.
 * @param callback Function to call when DSP's status changes.
 */
void dspHook(dspHookCookie* cookie, dspHookFn callback);

/**
 * @brief Removes a DSP status hook.
 * @param cookie Hook cookie to remove.
 */
void dspUnhook(dspHookCookie* cookie);

/**
 * @brief Checks if a headphone is inserted.
 * @param is_inserted Pointer to output the insertion status to.
 */
Result DSP_GetHeadphoneStatus(bool* is_inserted);

/**
 * @brief Flushes the cache
 * @param address   Beginning of the memory range to flush, inside the Linear or DSP memory regions
 * @param size      Size of the memory range to flush
 *
 * Flushes the cache for the specified memory range and invalidates the cache
 */
Result DSP_FlushDataCache(const void* address, u32 size);

/**
 * @brief Invalidates the cache
 * @param address   Beginning of the memory range to invalidate, inside the Linear or DSP memory regions
 * @param size      Size of the memory range to flush
 *
 * Invalidates the cache for the specified memory range
 */
Result DSP_InvalidateDataCache(const void* address, u32 size);

/**
 * @brief Retrieves the handle of the DSP semaphore.
 * @param semaphore Pointer to output the semaphore to.
 */
Result DSP_GetSemaphoreHandle(Handle* semaphore);

/**
 * @brief Sets the DSP hardware semaphore value.
 * @param value Value to set.
 */
Result DSP_SetSemaphore(u16 value);

/**
 * @brief Masks the DSP hardware semaphore value.
 * @param mask Mask to apply.
 */
Result DSP_SetSemaphoreMask(u16 mask);

/**
 * @brief Loads a DSP binary and starts the DSP
 * @param component The program file address in memory
 * @param size      The size of the program
 * @param prog_mask DSP memory block related ? Default is 0xff.
 * @param data_mask DSP memory block related ? Default is 0xff.
 * @param is_loaded Indicates if the DSP was succesfully loaded.
 *
 * @note The binary must be signed (http://3dbrew.org/wiki/DSP_Binary)
 * @note Seems to be called when the 3ds leaves the Sleep mode
 */
Result DSP_LoadComponent(const void* component, u32 size, u16 prog_mask, u16 data_mask, bool* is_loaded);

///Stops the DSP by unloading the binary.
Result DSP_UnloadComponent(void);

/**
 * @brief Registers an event handle with the DSP through IPC
 * @param handle Event handle to register.
 * @param interrupt The type of interrupt that will trigger the event. Usual value is DSP_INTERRUPT_PIPE.
 * @param channel The pipe channel. Usual value is 2
 *
 * @note It is possible that interrupt are inverted
 */
Result DSP_RegisterInterruptEvents(Handle handle, u32 interrupt, u32 channel);

/**
 * @brief Reads a pipe if possible.
 * @param channel     unknown. Usually 2
 * @param peer        unknown. Usually 0
 * @param buffer      The buffer that will store the values read from the pipe
 * @param length      Length of the buffer
 * @param length_read Number of bytes read by the command
 */
Result DSP_ReadPipeIfPossible(u32 channel, u32 peer, void* buffer, u16 length, u16* length_read);

/**
 * @brief Writes to a pipe.
 * @param channel unknown. Usually 2
 * @param buffer  The message to send to the DSP process
 * @param length  Length of the message
 */
Result DSP_WriteProcessPipe(u32 channel, const void* buffer, u32 length);

/**
 * @brief Converts a DSP memory address to a virtual address usable by the process.
 * @param dsp_address Address to convert.
 * @param arm_address Pointer to output the converted address to.
 */
Result DSP_ConvertProcessAddressFromDspDram(u32 dsp_address, u32* arm_address);

/**
 * @brief Reads a DSP register
 * @param regNo Offset of the hardware register, base address is 0x1EC40000
 * @param value Pointer to read the register value to.
 */
Result DSP_RecvData(u16 regNo, u16* value);

/**
 * @brief Checks if you can read a DSP register
 * @param regNo Offset of the hardware register, base address is 0x1EC40000
 * @param is_ready Pointer to write the ready status to.
 *
 * @warning This call might hang if the data is not ready. See @ref DSP_SendDataIsEmpty.
 */
Result DSP_RecvDataIsReady(u16 regNo, bool* is_ready);

/**
 * @brief Writes to a DSP register
 * @param regNo Offset of the hardware register, base address is 0x1EC40000
 * @param value Value to write.
 *
 * @warning This call might hang if the SendData is not empty. See @ref DSP_SendDataIsEmpty.
 */
Result DSP_SendData(u16 regNo, u16 value);

/**
 * @brief Checks if you can write to a DSP register ?
 * @param regNo Offset of the hardware register, base address is 0x1EC40000
 * @param is_empty Pointer to write the empty status to.
 */
Result DSP_SendDataIsEmpty(u16 regNo, bool* is_empty);
