// Capítulo 8: Diseño descendente

// Fichero formula1.cpp
// El siguiente programa realiza
// la gestión del campeonato
// de Fórmula 1

#include <iostream>
#include <stdlib.h>

using namespace std;

// Definición de constantes

const int MAXGPs = 20;
const int MAXCONSTRUCTORES = 12;
const int MAXPILOTOS = 60;
const int MAXNOMGP = 40;
const int MAXNOMCONSTRUCTOR = 40;
const int MAXNOMPILOTO = 40;
const int MAXOPCION = 15;
const int PUNTUABLES = 8;
const int puntos[PUNTUABLES] = {10,8,6,5,4,3,2,1};

// Estructuras de memoria

typedef char tOpcion[MAXOPCION];

typedef char tNombrePiloto[MAXNOMPILOTO];

typedef char tNombreGP[MAXNOMGP];

typedef char tNombreConstructor[MAXNOMCONSTRUCTOR];

struct tGranPremio {
    tNombreGP nomGP;
    tNombrePiloto ganador;
};

// Estructura para la información de los grandes premios
struct tListaGrandesPremios{
    tGranPremio GPs[MAXGPs];
    int nGPs;
};

// Tipo básico para la información de constructores
struct tConstructor {
    tNombreConstructor nom;
    int puntuacion;
    int posicionEnlaClas;
    int piloto1, piloto2;
};

// Estructura para la información de los constructores y su clasificación
struct tListaConstructores {
    tConstructor constructores[MAXCONSTRUCTORES];
    int clasificacion[MAXCONSTRUCTORES];
    int nConstructores;
};

// Tipo básico para la información de pilotos
struct tPiloto {
    tNombrePiloto nom;
    int puntuacion;
    int posicionEnlaClas;
    int indiceConstructor;
};

// Estructura para los pilotos y sus clasificaciones
struct tListaPilotos {
    tPiloto pilotos[MAXPILOTOS];
    int clasificacion[MAXPILOTOS];
    int nPilotos;
};

//Estructura para los campeonatos
struct tFormula1 {
    int anno;
    tListaGrandesPremios GPs;
    int nGPsCelebrados;
    tListaConstructores constructores;
    tListaPilotos pilotos;
};

// Añadir Gran Premio
void insertarGP(tListaGrandesPremios & GPs, tNombreGP nomGP) {
    if(GPs.nGPs < MAXGPs){
        strcpy(GPs.GPs[GPs.nGPs].nomGP,nomGP);
        strcpy(GPs.GPs[GPs.nGPs].ganador,"");
        GPs.nGPs++;
    }
}

//Leer Grandes Premios
void leerGPs(tListaGrandesPremios & GPs) {
    tNombreGP nomGP;
    cin >> nomGP;
    while(strcmpi(nomGP,"FIN_GP")!=0) {
        insertarGP(GPs,nomGP);
        cin >> nomGP;
    }
}

//Añadir piloto
void insertarPiloto(tListaPilotos & pilotos, tNombrePiloto nomPiloto,
                    int indiceConstructor) {
    strcpy(pilotos.pilotos[pilotos.nPilotos].nom,nomPiloto);
    pilotos.pilotos[pilotos.nPilotos].indiceConstructor=indiceConstructor;
    pilotos.pilotos[pilotos.nPilotos].puntuacion = 0;
    pilotos.pilotos[pilotos.nPilotos].posicionEnlaClas = pilotos.nPilotos;
    pilotos.clasificacion[pilotos.nPilotos] = pilotos.nPilotos;
    pilotos.nPilotos++;
}

//Añadir constructor
void insertarConstructor(tListaConstructores & constructores,
                         tNombreConstructor nomConstr,
                         int piloto1, int piloto2) {
    strcpy(constructores.constructores[constructores.nConstructores].nom,
           nomConstr);
    constructores.constructores[constructores.nConstructores].piloto1 =
                                                               piloto1;
    constructores.constructores[constructores.nConstructores].piloto2 =
                                                               piloto2;
    constructores.constructores[constructores.nConstructores].puntuacion=0;
    constructores.constructores[constructores.nConstructores].
                               posicionEnlaClas = constructores.nConstructores;
    constructores.clasificacion[constructores.nConstructores] =
    constructores.nConstructores;
    constructores.nConstructores++;
}

//Añadir pilotos de un constructor
void insertarConstrPilotos(tListaConstructores & constructores,
                           tListaPilotos & pilotos,
                           tNombreConstructor nomConstr,
                           tNombrePiloto nomPiloto1,
                           tNombrePiloto nomPiloto2) {
    int ic;
    ic = constructores.nConstructores;
    insertarConstructor(constructores,nomConstr,pilotos.nPilotos,
                        pilotos.nPilotos+1);
    insertarPiloto(pilotos,nomPiloto1,ic);
    insertarPiloto(pilotos,nomPiloto2,ic);
}

void leerConstrsPilotos(tListaConstructores & constructores,
                        tListaPilotos & pilotos) {
    tNombreConstructor nomConstr;
    tNombrePiloto nomPilot1, nomPilot2;
    cin >> nomConstr;
    while(strcmpi(nomConstr,"FIN_CONS")!=0) {
        cin >> nomPilot1 >> nomPilot2;
        insertarConstrPilotos(constructores,pilotos,nomConstr,
                              nomPilot1,nomPilot2);
        cin >> nomConstr;
    }
}

void inicializarClasificacionConstrs(tListaConstructores &
                                     constructores) {
    int i;
    i = 0;
    while(i < constructores.nConstructores) {
        constructores.clasificacion[i] = i;
        i++;
    }
}

void inicializarClasificacionPilotos(tListaPilotos &pilotos) {
    int i;
    i = 0;
    while(i < pilotos.nPilotos){
        pilotos.clasificacion[i] = i;
        i++;
    }
}

void inicializar(tFormula1 & F1) {
    tNombreGP nomGP;
    cin >> F1.anno;
    F1.nGPsCelebrados = 0;
    F1.GPs.nGPs = 0;
    F1.constructores.nConstructores = 0;
    F1.pilotos.nPilotos = 0;
    leerGPs(F1.GPs);
    leerConstrsPilotos(F1.constructores,F1.pilotos);
}

void escribirGP(const tGranPremio & GP) {
    cout << GP.nomGP << ' ' << GP.ganador << endl;
}

void escribirGPs(const tListaGrandesPremios & GPs) {
    int i;
    i=0;
    while(i < GPs.nGPs) {
        escribirGP(GPs.GPs[i]);
        i++;
    }
}

void escribirConstrsPilotos(const tListaConstructores & constructores,
                            const tListaPilotos & pilotos) {
    int i;
    i = 0;
    while(i < constructores.nConstructores) {
        cout << constructores.constructores[i].nom << ' '
             << pilotos.pilotos[constructores.constructores[i].piloto1].nom
             << ' '
             << pilotos.pilotos[constructores.constructores[i].piloto2].nom
             << endl;
        i++;
    }
}

int buscarPiloto(const tListaPilotos & pilotos,
                 tNombrePiloto nomPiloto) {
    bool encontrado;
    int i;
    encontrado = false;
    i = 0;
    while(i < pilotos.nPilotos && !encontrado) {
        if(strcmpi(nomPiloto,pilotos.pilotos[i].nom)== 0) {
            encontrado = true;
        }
        else {
            i++;
        }
    }
    if (!encontrado) {
        i = -1;
    }
    return i;
}

void actualizarClasificacionPilotos(tListaPilotos & pilotos, int p) {
    bool encontrado;
    int i;
    encontrado = false;
    i= pilotos.pilotos[p].posicionEnlaClas;
    while (i > 0 && !encontrado){
        if(pilotos.pilotos[pilotos.clasificacion[i-1]].puntuacion >=
           pilotos.pilotos[p].puntuacion) {
               encontrado = true;
        }
        else {
            pilotos.pilotos[pilotos.clasificacion[i-1]].posicionEnlaClas = i;
            pilotos.clasificacion[i] = pilotos.clasificacion[i-1];
            pilotos.clasificacion[i-1] = p;
            pilotos.pilotos[p].posicionEnlaClas = i-1;
            i--;
        }
    }
}

void actualizarClasificacionConstrs(tListaConstructores &constructores,
                                    int c) {
    bool encontrado;
    int i;
    encontrado = false;
    i= constructores.constructores[c].posicionEnlaClas;
    while (i > 0 && !encontrado) {
        if(constructores.constructores
           [constructores.clasificacion[i-1]].puntuacion
           >= constructores.constructores[c].puntuacion) {
              encontrado = true;
        }
        else {
            constructores.constructores[constructores.clasificacion[i-1]]
                                               .posicionEnlaClas = i;
            constructores.clasificacion[i] = constructores.clasificacion[i-1];
            constructores.clasificacion[i-1] = c;
            constructores.constructores[c].posicionEnlaClas = i-1;
            i--;
        }
    }
}

void leerClasificacionGPYActualizarF1(tFormula1 &F1) {
    tNombrePiloto nomPiloto;
    int p,ic,cont;
    cont = 0;
    cin >> nomPiloto;
    strcpy(F1.GPs.GPs[F1.nGPsCelebrados].ganador,nomPiloto);
    F1.nGPsCelebrados++;
    while(strcmpi(nomPiloto,"FIN_CL")!=0 && cont < PUNTUABLES) {
        p = buscarPiloto(F1.pilotos,nomPiloto);
        F1.pilotos.pilotos[p].puntuacion =
                       F1.pilotos.pilotos[p].puntuacion + puntos[cont];
        actualizarClasificacionPilotos(F1.pilotos,p);
        ic = F1.pilotos.pilotos[p].indiceConstructor;
        F1.constructores.constructores[ic].puntuacion =
                  F1.constructores.constructores[ic].puntuacion + puntos[cont];
        actualizarClasificacionConstrs(F1.constructores,ic);
        cont++;
        cin >> nomPiloto;
    }
    while(strcmpi(nomPiloto,"FIN_CL")!=0){
        cin >> nomPiloto;
    }
}

bool esPilotoOficial(const tConstructor & constructor, int p) {
    return p == constructor.piloto1 || p == constructor.piloto2;
}

bool mismoConstructor(const tListaPilotos & pilotos, int p1, int p2) {
    return pilotos.pilotos[p1].indiceConstructor ==
           pilotos.pilotos[p2].indiceConstructor;
}

void sustituirPiloto(tConstructor & constructor, int p1, int p2) {
    if(constructor.piloto1 == p1) {
        constructor.piloto1 = p2;
    }
    else if(constructor.piloto2 == p1) {
        constructor.piloto2 = p2;
    }
}

void sustitucion(tFormula1 &F1) {
    tNombrePiloto nomPiloto1,nomPiloto2;
    int p1, p2, ic;
    cin >> nomPiloto1 >> nomPiloto2;
    p1 = buscarPiloto(F1.pilotos, nomPiloto1);
    if(p1 < 0){
        cout << "ERROR PILOTO INEXISTENTE" << endl;
    }
    else{
        ic = F1.pilotos.pilotos[p1].indiceConstructor;
        if (!esPilotoOficial(F1.constructores.constructores[ic],p1)) {
            // ya no es piloto oficial
            cout << "ERROR NO ES PILOTO OFICIAL" << endl;
        }
        else {
            p2 = buscarPiloto(F1.pilotos, nomPiloto2);
            if(p2 >= 0) {
                if(!mismoConstructor(F1.pilotos,p1,p2)) {
                    cout << "ERROR PILOTO DE OTRO CONSTRUCTOR" << endl;
                }
                else {
                    sustituirPiloto(F1.constructores.constructores[ic],p1,p2);
                }
            }
            else {
                p2 = F1.pilotos.nPilotos;
                insertarPiloto(F1.pilotos,nomPiloto2,ic);
                sustituirPiloto(F1.constructores.constructores[ic],p1,p2);
            }
        }
    }
}

void escribirClasificacionPilotos(const tFormula1 & F1) {
    int i;
    i = 0;
    while(i < F1.pilotos.nPilotos) {
        cout << F1.pilotos.pilotos[F1.pilotos.clasificacion[i]].nom << ' '
             << F1.constructores.constructores[F1.pilotos.pilotos
                   [F1.pilotos.clasificacion[i]].indiceConstructor].nom << ' '
             << F1.pilotos.pilotos[F1.pilotos.clasificacion[i]].puntuacion
             << endl;
        i++;
    }
}

void escribirClasificacionConstructores(const tListaConstructores
                                        &constructores) {
    int i;
    i = 0;
    while(i < constructores.nConstructores) {
        cout << constructores.constructores[constructores.clasificacion[i]].nom
             << ' '
             << constructores.constructores[constructores.clasificacion[i]].puntuacion
             << endl;
        i++;
    }
}

int buscarConstructor(const tListaConstructores & constructores,
                      tNombreConstructor nomConstructor) {
    bool encontrado;
    int i;
    encontrado = false;
    i = 0;
    while(i < constructores.nConstructores && !encontrado) {
        if(strcmpi(nomConstructor,constructores.constructores[i].nom)== 0) {
            encontrado = true;
        }
        else {
            i++;
        }
    }
    if (!encontrado) {
        i = -1;
    }
    return i;
}

void escribirOficiales(const tFormula1 & F1) {
    int p;
    tNombreConstructor nomConstructor;
    cin >> nomConstructor;
    p = buscarConstructor(F1.constructores, nomConstructor);
    if(p >= 0) {
        cout << F1.constructores.constructores[p].nom
             << ' '
             << F1.pilotos.pilotos[F1.constructores.constructores[p].piloto1].nom
             << ' '
             << F1.pilotos.pilotos[F1.constructores.constructores[p].piloto2].nom
             << ' ' << endl;
    }
}

// Descomposición del subprograma tratarOpcion en seis subprogramas más simples
//
// escribirClasificacionPilotos
// ecribirClasificacionConstructores
// sustitucion
// escribirGPs
// escribirOficiales
// leerClasificacionGPYActualizarF1
void tratarOpcion(tOpcion opcion, tFormula1 &F1) {
    if(strcmpi(opcion,"PILOTOS")==0) {
        escribirClasificacionPilotos(F1);
    }
    else if (strcmpi(opcion,"CONSTRUCTORES")==0) {
        escribirClasificacionConstructores(F1.constructores);
    }
    else if (strcmpi(opcion,"SUSTITUCION")==0) {
        sustitucion(F1);
    }
    else if (strcmpi(opcion,"GRANDES_PREMIOS")==0) {
        escribirGPs(F1.GPs);
    }
    else if(strcmpi(opcion,"CLAS_GP")==0) {
        leerClasificacionGPYActualizarF1(F1);
    }
    else if(strcmpi(opcion,"OFICIALES")==0) {
        escribirOficiales(F1);
    }
}

// Función principal del programa en la que se ha
// aplicado un esquema de recorrido
int main() {
    tFormula1 F1;
    tOpcion opcion;
    inicializar(F1);
    cin >> opcion;
    while(strcmpi(opcion,"FIN")!=0) {
        tratarOpcion(opcion,F1);
        cin >> opcion;
    }
    return 0;
}