//Numéro de sprite utilisé pour la boîte de dialogue.
int num_spr_dialogue;
int bCPUMetre=0, bTransp=1;

//Les informations sont composées d'une zone et d'un identificateur (n°) d'événement.
typedef struct      {
    u32 x0,y0,x1,y1,event;
} INFO;

#include "menupause.h"

//Evenement spécial pour le menu pause
EVENEMENT evPause=      {
    "Menu pause",
    "#|Reprendre la partie#Reprendre\n#|Changer les couleurs de l'environnement#Menu couleurs...\n   Musique: #|Active ou non la musique#\n   Bruitages: #|Active ou non les bruitages#\n   Morceau: #|Faites votre choix#\n   CPU-Mètre: #|Affiche l'utilisation du processeur#",
    FctMenuPause,
};

//Contient la liste des zones d'informations et les événements générés par le programme à Zanella.
#include "zones.h"


//Teste à l'endroit où se trouve le curseur c s'il y a une boîte d'information qui s'y trouve et retourne,
//le cas échéant, un pointeur sur celle-ci. On la dessinera par la suite.
INFO *TesteInfos(CURSEUR *c)       {
    u32 i;
    //Pour toutes les infos existantes
    for (i=0;i<sizeof(infos)/sizeof(infos[0]);i++)      {
        //Teste si le curseur se trouve dans le rectangle de la boîte d'infos
        if (c->x>=infos[i].x0 && c->x<=infos[i].x1 && c->y>=infos[i].y0 && c->y<=infos[i].y1)
            //On est sur cette info-ci.
            return (INFO*)&infos[i];
    }
    //Aucune info trouvée
    return NULL;
}

// Utilisée par AffichePoints.
void Affiche1Point(s32 x, s32 y, u8 type, BOOL select)       {
    //Crée un nouvel objet
    CreeObjetStandard(AjouteSprite(point_tiles+16*8*select,16*16),1,TAILLE_16x16);
    PlaceObjetEx(objetsUtilises,x,y);
}

//Affiche tous les points (objets ronds) visibles sur l'écran, indiquant une boîte d'infos.
BOOL AffichePoints(CURSEUR *c, ECRAN *e, INFO *selection)        {
    u32 i;
    s32 x,y, x_point, y_point;
    BOOL select=FALSE;

    //Pour toutes les infos disponibles
    for (i=0;i<sizeof(infos)/sizeof(infos[0]);i++)      {
        //On trouve le point milieu
        x_point=((infos[i].x0+infos[i].x1)>>1)-8;
        y_point=((infos[i].y0+infos[i].y1)>>1)-8;
        //On y soustrait la position de l'écran pour trouver la position relative
        x=x_point-e->x;
        y=y_point-e->y;
        //Si le point est contenu dans l'écran
        if (x>=-16 && x<240 && y>=-16 && y<160)         {
            //Si c'est le point sélectionné
            if (selection==&infos[i])       {
                //On l'affiche avec des tiles différentes utilisant une palette clignotante
                Affiche1Point(x,y,0,1);
                select=TRUE;
            }
            else
                //Sinon avec la palette de base (bleue, normale)
                Affiche1Point(x,y,0,0);
        }
    }
    //Retourne si oui ou non une sélection est faite. Permet de savoir s'il y a besoin de mettre à jour l'affichage.
    return select;
}

//Initialise le système de boîte de dialogue, principalement en initialisant le sprite transparent et en
//rotation de celle-ci.
void InitBteDialogue()      {
    int i;
    //Voir mon système de sprites (sprites.h)
    i=AjouteSprite(noir_tiles,64*64*2);
    //Fonction spéciale de sprites.h juste prévue pour cela (modification de CreeObjetStandard).
//    CopieDMA((void*)noir_palette,nPalObj(2),16,DMA_16im);
    CreeBoite(i,TAILLE_64x64);
    //On la place à l'endroit par défaut
    PlaceObjetXY(objetsUtilises,14*8,3*8);
    //On initialise le rotset n° 0 (qu'utilise ce sprite)
    rotData[0].pb=0;                            //dmx } ces paramètres ne sont utilisés que pour la rota-
    rotData[0].pc=0;                            //dmy } tion. Le redimensionnement n'utilise que dx et dy.
    //On garde une trace (globale) du numéro de sprite étant donné qu'on va y accéder depuis un peu partout.
    num_spr_dialogue=objetsUtilises;
}

//Déplace la boîte de dialogue.
void PlaceBteDialogue(u32 x, u32 y)      {
    //Il faut non seulement déplacer le sprite ...
    PlaceObjetEx(num_spr_dialogue,x,y-(64-y_ww)/2-32);
    //... mais aussi tout le BG1. On le fait en modifiant les valeurs du défilement.
    REG_BG1HOFS=-x, REG_BG1VOFS=-y;
}

//Endroit actuel où se trouve la boîte de dialogue
u32 endroit_x, endroit_y;

//Gestion de la boîte de dialogue en fonction du curseur, de la position voulue et de l'écran. Permet que
//les deux ne s'écrasent pas mutuelement. Le paramètre set permet de définir la position étant donné que
//la fonction va amortir le mouvement au cas où il est très brusque (c'est plus joli).
void PlacementBteDialogue(CURSEUR *c, ECRAN *e, BOOL set)       {
    //Amplitude de mouvement à chaque cycle
    const u32 amplitude=8;
    u32 i;
    //Positions actuelles, sauvées.
    static int cur_x=0, cur_y=0;
    s32 x, y;

    //Calcule la position idéale
    y=c->y-e->y+c->h+5;
    x=c->x-e->x-64+c->l/2;
    //Hors de l'écran -> on la met ailleurs...
    if (x<0)            x=0;
    if (x+128>=240)     x=240-128;
    if (y+y_ww>=150)        {
        y=c->y-e->y-y_ww-5;
    }
    if (y<0)            y=0;

    //Pas de déplacement fluide dans ce cas (initialisation)
    if (set)
        cur_x=x, cur_y=y;

    for (i=0;i<amplitude;i++)           {
        //Déplace lentement la boîte vers son objectif
        if (x>cur_x)
            cur_x++;
        if (x<cur_x)
            cur_x--;
        if (y>cur_y)
            cur_y++;
        if (y<cur_y)
            cur_y--;
    }
    //Et voilà.
    PlaceBteDialogue(cur_x,cur_y);
    //Il faut garder ces valeurs en tant que globales.
    endroit_x=cur_x, endroit_y=cur_y;
}

//Informations sur la zone de texte zt_infos
ZONE_TEXTE zt_infos={buffer_infos,16,10};
int gblOffset=0;

//Dessine la boîte d'informations. Utilise une couleur différente pour le projet sélectionné (n° ID = selection).
void DessineInfo(const EVENEMENT *ev, s32 selection)        {
    static int taille_max=0;
    
    //En fonte moyenne
    ChangeFonte(Moyenne);
    //Avec la zone de texte zt_infos
    SelectZoneTexte(&zt_infos);
    //Effacement de la zone
    EffaceZone(&zt_infos);
    //Dessine le titre
    DessTexte(0,0,ev->titre,2);
    //Deux fois pour donner un effet de gras
    DessTexte(1,0,ev->titre,2);
    //Maintenant en Arial (plus petite)
    ChangeFonte(Arial);

    //Commenter ces deux lignes pour désactiver la préanalyse du menu. Dans la majorité des cas, cela
    //fonctionnera sans problème (sauf dans le cas de menus à handlers avec des options changeant
    //subitement de taille lorsqu'elles sont sélectionnées). Cela permet d'économiser un temps CPU
    //non négligeable!
    DessTexteSpecial(2,13,ev->texte,3,-1,0,ev->fonction,0);
    taille_max=y_ww;

    /*
        Cette partie détermine le meilleur scrolling (gblOffset) possible selon la sélection active.
        Normalement, quatre lignes suffiraient pour des menus basiques, mais avec le support de texte
        statique au sommet et à la fin de la boîte qui devrait toujours être visible et que tous les cas
        que j'ai pu imaginer sont supportés, le code est légèrement plus long.
        Si vous souhaitez le modifier et que vous ne le comprenez vraiment pas, alors mieux vaut le
        réécrire vous-même!
    */
    //Pas de sélection -> affichage de la première ligne
    if (selection<0)
        gblOffset=0;
    //Premier lien sélectionné
    else if (selection==0)            {
        //Dans la mesure du possible, on affiche le texte au sommet
        gblOffset=0;
        //Et si la taille du texte au sommet + le lien sélectionné > hauteur de la boîte, on fait défiler
        //la boîte pour voir au moins entièrement le lien.
        while (liens[selection].yfin>=64+gblOffset)
            gblOffset+=hauteur_texte;
    }
    //Dernière option sélectionnée
    else if (selection==nEvenement-1)      {
        //Place le défilement de manière à voir le texte jusqu'à la dernière ligne.
        while(taille_max+hauteur_texte>=64+gblOffset)
            gblOffset+=hauteur_texte;
        //Mais si cela ne permet pas de voir le lien entièrement (i.e. il est plus haut que le sommet de
        //la boîte), on remonte le défilement. La priorité est toujours qu'on voie le lien sélectionné!
        while (liens[selection].y<=13+gblOffset-hauteur_texte)
            gblOffset-=hauteur_texte;
    }
    //Une option du milieu est sélectionnée
    else    {
        while(1)        {
            //Contrôle si les deux conditions sont vraies (ce qui provoquerait un plantage), c'est-à-dire
            //que le fond du texte est en-dessous de la barre et le sommet en-dessus également. Comme il y
            //a trois lignes entre-deux, on teste >3*hauteur_texte.
            if (liens[selection].yfin-liens[selection].y>3*hauteur_texte)       {
                //Le lien fait 4 lignes ou plus -> aligné sur le haut
                gblOffset=liens[selection].y-13;
                break;
            }
            //Fond du lien plus bas que la quatrième ligne -> défilement sur le haut
            else if (liens[selection].yfin+hauteur_texte>=64+gblOffset)
                gblOffset+=hauteur_texte;
            //Sommet du lien plus haut que la deuxième ligne -> défilement sur le bas
            else if (liens[selection].y<=13+gblOffset)
                gblOffset-=hauteur_texte;
            //Plus aucun problème.
            else
                break;
        }
    }
    //La dernière ligne doit toujours se trouver au fond!
    while (taille_max-gblOffset+2*hauteur_texte<64)
        gblOffset-=hauteur_texte;
    //Mais la première doit aussi toujours se trouver au sommet... et c'est quand-même une priorité.
    if (gblOffset<0)
        gblOffset=0;

    ChangeFonte(Arial);
    //Dessine le texte avec le lien sélectionné
    DessTexteSpecial(2,13,ev->texte,3,selection,gblOffset,ev->fonction,1);

    //Permet de connaître la taille totale de la zone de texte (ce paramètre est modifié par la fonction ci-dessus).
    taille_max=y_ww;
    //Hauteur de la boîte de dialogue.
    if (y_ww+hauteur_texte>64)
        y_ww=14+5*hauteur_texte;                //Pas plus grande que 64 de toutes façons!
    else
        y_ww+=hauteur_texte+1;                  //Mais peut-être plus petite.
}

//Comme dans la boucle principale
int BougeMain(int no_obj, int x, int y, int vitesse);
void Main_EffetsPalette();
#define EffaceBufferDebug()  MemSet(&zero,(void*)CharBaseBlock(3)+32,sizeof(buffer_debug)>>1,DMA_16im)

int PlaceMiniMessage(int actif)             {
    static int posY=0;
    int ancY=posY;
    
    if (actif)      {
        if (posY<11)
            posY++;
    }
    else    {
        if (posY>0)
            posY--;
    }
    
    if (posY!=ancY)                     //La position a changé?
        REG_BG3VOFS=-160+posY;          //On écrit sur l'arrière-plan
    return posY;
}

void ChangeCouleursBteDialogue()         {
    int rsrc=couleurs[0][0], vsrc=couleurs[0][1], bsrc=couleurs[0][2];
    int rdst=couleurs[1][0], vdst=couleurs[1][1], bdst=couleurs[1][2];
    int i,j;
    
    for (i=0;i<32;i++)          {
        j=32-i;
        PaletteObj[32+i]=RGB((i*rdst+j*rsrc)>>5,(i*vdst+j*vsrc)>>5,(i*bdst+j*bsrc)>>5);
    }
    Palette[195]=RGB(couleurs[2][0],couleurs[2][1],couleurs[2][2]);
    Palette[194]=RGB(couleurs[3][0],couleurs[3][1],couleurs[3][2]);
    Palette[197]=RGB(couleurs[6][0],couleurs[6][1],couleurs[6][2]);
    Palette[193]=RGB(couleurs[7][0],couleurs[7][1],couleurs[7][2]);
}

int lastnEv=0;

//Affiche et gère le menu de sélection des projets.
int SelectionneProjet(CURSEUR *c, const EVENEMENT *ev)            {
    //Numéro d'événement sélectionné
    int nEv=0, activite_message=0;
    //Mini-système de gestion des touches meilleur que la lecture à bas niveau.
    int bien_place=0, TOUCHES, repete=0;
    u32 zero=0x11111111;
    int i;

    //Dans le cas d'une mise à jour du sélecteur de projet, on doit laisser la sélection à son état actuel, sinon la réinitialiser.
    if (action==1)
        nEv=lastnEv;

    //Retourne la main
    RetourneObjetH_Oui(c->no_sprite);
    
    gblCurs=c;

    //Il faut commencer par une mise à jour!
    goto maj;

    while(1)        {
        //Cette petite manipulation permet de ne récupérer que les touches qui viennent d'être préssées (de passer à 1)
        TOUCHES=(REG_TOUCHES)&~ancienRegTouches;
        ancienRegTouches=REG_TOUCHES;
        if (TOUCHES)
            repete=0;                       //Changement de touche -> désactive la répétition pour le moment
        else
            repete++;                       //Aucun changement de touche? Si ça continue, on enclenchera la répétition des touches
        if (repete>30 && repete%8==0)
            TOUCHES=REG_TOUCHES&0x03f0;

        if (ev->fonction)       {
            i=(int)ev->fonction(DEM_GESTION,nEv,TOUCHES);
            if (i==0)
                goto maj;
            else if (i==2)
                goto boutonB;
        }
        
        //Si au moins une touche a été appuyée, cela vaut la peine de mettre à jour l'affichage, autrement mieux vaut ne pas trop se fatiguer...
        if (TOUCHES)            {
            if (TOUCHES&TOUCHE_BAS)     {
                nEv++;
                JoueSon("curs1.son");
            }
            else if (TOUCHES&TOUCHE_HAUT)       {
                JoueSon("curs1.son");
                nEv--;
            }
            else if (TOUCHES&TOUCHE_A)          {
                //Lit le texte associé au lien sélectionné
                if (liens[nEv].symbole[0]==0)     {                 //Pas de lien défini -> on demande une action au handler
                    i=(int)ev->fonction(DEM_ACTION,nEv,TOUCHES);
                    if (i==0)
                        goto maj;
                    else if (i==2)
                        goto boutonB;
                }
                else    {
                    JoueSon("curs2.son");
                    LTMain(liens[nEv].symbole);
                    lastnEv=nEv;
                    //Mise à jour nécessaire
                    return 1;
                }
            }
            else if (TOUCHES&TOUCHE_B)          {
                JoueSon("curs3.son");
boutonB:
                //Pas de mise à jour requise
                return 0;
            }

            //Vu l'implémentation du modulo sur cette version de GCC, je ne peux pas l'utiliser pour les nombres négatifs.
            //Si le curseur est tout en bas -> tout en haut.
            while(nEv<0)                nEv+=nEvenement;
            while(nEv>=nEvenement)      nEv-=nEvenement;

maj:
            //Met à jour l'afficheur de texte en fonction de la sélection actuelle
            DessineInfo(ev,nEv);
            activite_message=message_fond[0];
        }

        //Selon que c'est un petit mouvement habituel (déplacement vertical) ou le mouvement de départ de la main, la vitesse de déplacement ne sera pas la même
        if (BougeMain(c->no_sprite,endroit_x-25,endroit_y+liens[nEv].y-3-gblOffset+(liens[nEv].yfin-liens[nEv].y)/2,bien_place?2:8))
            bien_place=TRUE;

        ChangeFonte(Arial);
        SelectZoneTexte(&zt_debug);

        if (message_fond[0])      {
            //Vide la zone de texte qui affiche le message d'info
            MemSet((void*)&zero,(void*)buffer_debug,30*2*8,DMA_32im);
            //Dessine le texte
            DessTexte(0,0,message_fond,5);
            //Pour ne le dessiner qu'une seule fois;
            message_fond[0]=0;
        }
        
        //Les tâches ci-dessous ne doivent pas interférer avec le balayage de l'écran
        AttendVSync();
        PlaceMiniMessage(activite_message);
        //Copie des sprites
        CopieDMA0(MonOAM,pOAM,1024>>2,DMA_32im);
        //Effets de palette principaux
        Main_EffetsPalette();
        //Prochaine image
        nFrame++;

        //Copie du texte d'informations
        CopieDMA((void*)buffer_infos,(void*)CharBaseBlock(2)+32,16*16*10,DMA_16im);
        CopieDMA((void*)buffer_debug,(void*)CharBaseBlock(3)+32,sizeof(buffer_debug)>>1,DMA_16im);
        //Remet l'écran au jour s'il est à l'état "nuit".
        FadeOut();
    }
}


