#include "plugin.h"

#include "fixedpoint.h"
#include "lib/helper.h"
#include "lib/pluginlib_exit.h"


#include "SDL.h"
#include "SDL_video.h"

#ifdef COMBINED_SDL
#error deprecated
#endif

/* SDL program */
extern int my_main(int argc, char *argv[]);

void *audiobuf = NULL;

unsigned int sdl_thread_id, main_thread_id;

bool printf_enabled = true;

static void (*exit_cb)(void) = NULL;

/* sets the exit handler presented to the program */
void rbsdl_atexit(void (*cb)(void))
{
    if(exit_cb)
    {
        rb->splash(HZ, "WARNING: multiple exit handlers!");
    }
    exit_cb = cb;
}

/* called by program */
void rb_exit(int rc)
{
    if(rb->thread_self() == main_thread_id) /* rockbox main thread */
        exit(rc);
    else
        rb->thread_exit();
}

/* exit handler, called by rockbox */
void cleanup(void)
{
    if(exit_cb)
        exit_cb();

    if(exit_cb != SDL_Quit)
        SDL_Quit();

#if defined(HAVE_LCD_COLOR) && !defined(SIMULATOR) && !defined(RB_PROFILE)
#define USE_TIMER
#endif

#ifdef USE_TIMER
    /* stop timer callback if there is one, since the memory it
     * resides in could be overwritten */
    rb->timer_unregister();
#endif

    if(audiobuf)
        memset(audiobuf, 0, 4); /* clear */

    backlight_use_settings();

#ifdef HAVE_ADJUSTABLE_CPU_FREQ
    rb->cpu_boost(false);
#endif
}

static long main_stack[1024 * 1024 / 4]; /* ie 1 MB */

int (*main_fn)(int argc, char *argv[]);
int prog_idx;
static void main_thread(void)
{
    char *fallback[] = { "/blah", NULL };

#ifdef COMBINED_SDL
    char **argv = programs[prog_idx].argv ? programs[prog_idx].argv : fallback;
    int argc = programs[prog_idx].argv ? programs[prog_idx].argc : 1;
#else
    char **argv = fallback;
    int argc = 1;
#endif

    int rc = main_fn(argc, argv);
    if(rc != 0)
        rb->splash(HZ * 2, SDL_GetError());

    rb->thread_exit();
}

#if defined(CPU_ARM) && !defined(SIMULATOR)
#define CR_M    (1 << 0)        /* MMU enable                           */
#define CR_A    (1 << 1)        /* Alignment abort enable               */
#define CR_C    (1 << 2)        /* Dcache enable                        */
#define CR_W    (1 << 3)        /* Write buffer enable                  */
#define CR_P    (1 << 4)        /* 32-bit exception handler             */
#define CR_D    (1 << 5)        /* 32-bit data address range            */
#define CR_L    (1 << 6)        /* Implementation defined               */
#define CR_B    (1 << 7)        /* Big endian                           */
#define CR_S    (1 << 8)        /* System MMU protection                */
#define CR_R    (1 << 9)        /* ROM MMU protection                   */
#define CR_F    (1 << 10)       /* Implementation defined               */
#define CR_Z    (1 << 11)       /* Implementation defined               */
#define CR_I    (1 << 12)       /* Icache enable                        */
#define CR_V    (1 << 13)       /* Vectors relocated to 0xffff0000      */
#define CR_RR   (1 << 14)       /* Round Robin cache replacement        */
#define CR_L4   (1 << 15)       /* LDR pc can set T bit                 */
#define CR_DT   (1 << 16)
#ifdef CONFIG_MMU
#define CR_HA   (1 << 17)       /* Hardware management of Access Flag   */
#else
#define CR_BR   (1 << 17)       /* MPU Background region enable (PMSA)  */
#endif
#define CR_IT   (1 << 18)
#define CR_ST   (1 << 19)
#define CR_FI   (1 << 21)       /* Fast interrupt (lower latency mode)  */
#define CR_U    (1 << 22)       /* Unaligned access operation           */
#define CR_XP   (1 << 23)       /* Extended page tables                 */
#define CR_VE   (1 << 24)       /* Vectored interrupts                  */
#define CR_EE   (1 << 25)       /* Exception (Big) Endian               */
#define CR_TRE  (1 << 28)       /* TEX remap enable                     */
#define CR_AFE  (1 << 29)       /* Access flag enable                   */
#define CR_TE   (1 << 30)       /* Thumb exception enable               */

#define __LINUX_ARM_ARCH__ ARM_ARCH

#if __LINUX_ARM_ARCH__ >= 7
#define isb(option) __asm__ __volatile__ ("isb " #option : : : "memory")
#define dsb(option) __asm__ __volatile__ ("dsb " #option : : : "memory")
#define dmb(option) __asm__ __volatile__ ("dmb " #option : : : "memory")
#elif defined(CONFIG_CPU_XSC3) || __LINUX_ARM_ARCH__ == 6
#define isb(x) __asm__ __volatile__ ("mcr p15, 0, %0, c7, c5, 4" \
                                    : : "r" (0) : "memory")
#define dsb(x) __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 4" \
                                    : : "r" (0) : "memory")
#define dmb(x) __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 5" \
                                    : : "r" (0) : "memory")
#elif defined(CONFIG_CPU_FA526)
#define isb(x) __asm__ __volatile__ ("mcr p15, 0, %0, c7, c5, 4" \
                                    : : "r" (0) : "memory")
#define dsb(x) __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 4" \
                                    : : "r" (0) : "memory")
#define dmb(x) __asm__ __volatile__ ("" : : : "memory")
#else
#define isb(x) __asm__ __volatile__ ("" : : : "memory")
#define dsb(x) __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 4" \
                                    : : "r" (0) : "memory")
#define dmb(x) __asm__ __volatile__ ("" : : : "memory")
#endif

static inline unsigned long get_cr(void)
{
        unsigned long val;
        asm("mrc p15, 0, %0, c1, c0, 0	@ get CR" : "=r" (val) : : "cc");
	return val;
}

static inline void set_cr(unsigned long val)
{
	asm volatile("mcr p15, 0, %0, c1, c0, 0	@ set CR"
	  : : "r" (val) : "cc");
	isb();
}
#endif

enum plugin_status plugin_start(const void *param)
{
    (void) param;

#if defined(CPU_ARM) && !defined(SIMULATOR) && (ARM_ARCH >= 5)
    /* (don't) set alignment trap. Will generate a data abort
     * exception on ARM. */
//    set_cr(get_cr() | CR_A);
#endif

#if 0
    char c = *((char*)NULL);

    /* test alignment trap */
    unsigned int x = 0x12345678;
    char *p = ((char*)&x) + 1;
    unsigned short *p2 = (unsigned short*)p;
    rb->splashf(HZ, "%04x, %02x%02x", *p2, *(p+1), *p);
#endif

    /* don't confuse this with the main SDL thread! */
    main_thread_id = rb->thread_self();

    /* we always use the audio buffer */
    size_t sz;
    audiobuf = rb->plugin_get_audio_buffer(&sz);
#if (CONFIG_PLATFORM & PLATFORM_NATIVE)
    /* We are loaded as an overlay towards the end of the audio
     * buffer. We must take care not to overwrite ourselves. */
    if ((uintptr_t)audiobuf < (uintptr_t)plugin_start_addr)
    {
        uint32_t tmp_size = (uintptr_t)plugin_start_addr - (uintptr_t)audiobuf;
        sz = MIN(sz, tmp_size);
    }
#endif

    /* wipe sig */
    rb->memset(audiobuf, 0, sz);
    if(init_memory_pool(sz, audiobuf) == (size_t) -1)
    {
        rb->splashf(HZ*2, "TLSF init failed!");
        return PLUGIN_ERROR;
    }

    main_fn = my_main;

#ifdef HAVE_ADJUSTABLE_CPU_FREQ
    rb->cpu_boost(true);
#endif

    backlight_ignore_timeout();

    /* set the real exit handler */
#undef rb_atexit
    rb_atexit(cleanup);

    /* make a new thread to use a bigger stack and higher priority than otherwise accessible */
    sdl_thread_id = rb->create_thread(main_thread, main_stack,
                                      sizeof(main_stack), 0, "sdl_main"
                                      IF_PRIO(, PRIORITY_USER_INTERFACE) // we need other threads still
                                      IF_COP(, CPU));
    rb->thread_wait(sdl_thread_id);

    rb->sleep(HZ); /* wait a second in case there's an error message on screen */

    return PLUGIN_OK;
}
