/**
 *   Løsningsforslag til eksamen i OOProg (i C++), august 2026, oppgave 2.
 *
 *   Programmet holder orden på en restaurant sine ulike kunder/gjester
 *   på ulike bord og deres bestillinger/kjøp.
 *
 *   Programmet:
 *      - skriver ALT om ETT bord
 *      - legger inn en ny kunde/gjest på et bord
 *      - registrerer bestilling/kjøp på et bord
 *      - lar kunde/gjest dra fra restauranten (sjekker ut og betaler)
 *      - samler og skriver nåværende bestillinger/kjøp totalt
 *      - leser HELE datastrukturen fra fil
 *      - skriver HELE datastrukturen til fil
 *
 *   @file     EX_S26_2.CPP
 *   @author   Frode Haug, NTNU
 */


#include <iostream>             //  cout, cin
#include <fstream>              //  ifstream, ofstream
#include <string>
#include <vector>
#include <map>
#include <cctype>               //  toupper, isdigit
#include "LesData2.h"           //  Verktøykasse for lesing av diverse data
using namespace std;


const int ANTBORD =   30;   ///<  Antall nummererte bord i restauranten.
const int RABATT  =   30;   ///<  Max. prosentrabatt for stamgjest/-kunde.
const int ANTALL  =   20;   ///<  Max. antall på ett bord.
const int RETTER  =   50;   ///<  Max. antall valg innen ulike retter/drikker.


/**
 *  Baseklassen 'Kunde' med dens navn, totalantallet vedkommendes bord og
 *  kjøpene (mat og drikke) som bordet totalt har foretatt.
 */
class Kunde  {
  private:
    string navn;            //  Hovedkundens/bestillerens navn.
    int  antall;            //  Totalantallet som er tilknyttet bordet.
    map <string, int> kjop; //  Kjøpene/bestillingene som bordet har foretatt.
    bool lovligKjop(string & str) const;                       //  Oppgave 2C
  public:
    Kunde()  { antall = 0;  }                                  //  Ferdiglaget
    Kunde(ifstream & inn);                                     //  Oppgave 2G
                                                               //  Oppgave 2E:
    void bestillinger(const char kode, map <string, int> & best) const;
    void kjopForetas();                                        //  Oppgave 2D
    virtual void lesData();                                    //  Oppgave 2B
    virtual void skrivData() const;                            //  Oppgave 2A
    virtual void skrivTilFil(ofstream & ut) const;             //  Oppgave 2F
    virtual void skrivTypeTilFil(ofstream & ut) const;         //  Oppgave 2F
};


/**
 *  Avledet klasse 'Stamgjest' med prosentrabatt og annen spesiell service.
 */
class Stamgjest : public Kunde  {
  private:
    int rabatt;             //  Rabatt (i prosent).
    string service;         //  Beskrivelse av ekstra service vedkommende får.
  public:
    Stamgjest()  {  rabatt = 0;  }                             //  Ferdiglaget
    Stamgjest(ifstream & inn);                                 //  Oppgave 2G
    virtual void lesData();                                    //  Oppgave 2B
    virtual void skrivData() const;                            //  Oppgave 2A
    virtual void skrivTilFil(ofstream & ut) const;             //  Oppgave 2F
    virtual void skrivTypeTilFil(ofstream & ut) const;         //  Oppgave 2F
};


void bestillingerTotalt();                                     //  Oppgave 2E
void kjop();                                                   //  Oppgave 2D
void lesFraFil();                                              //  Oppgave 2G
void nyKunde();                                                //  Oppgave 2B
void skrivEttBord();                                           //  Oppgave 2A
void skrivMeny();                                              //  Ferdiglaget
void skrivTilFil();                                            //  Oppgave 2F


vector <Kunde*> gBordene(ANTBORD);          ///<  ALLE bordene i restauranten.


/**
 *  Hovedprogrammet.
 */
int main() {
  char valg;
  int nr;

  lesFraFil();                                                 //  Oppgave 2G

  skrivMeny();
  valg = lesChar("\nKommando");

  while (valg != 'Q') {
     switch (valg) {
       case 'S': skrivEttBord();              break;           //  Oppgave 2A
       case 'N': nyKunde();                   break;           //  Oppgave 2B
       case 'K': kjop();                      break;           //  Oppgave 2D
       case 'B': bestillingerTotalt();        break;           //  Oppgave 2E
       default:  skrivMeny();                 break;
     }
     valg = lesChar("\nKommando");
  }

  skrivTilFil();                                               //  Oppgave 2F

  cout << "\n\n";

  return 0;
}



// ---------------------------------------------------------------------------
//                       DEFINISJON AV KLASSE-FUNKSJONER:
// ---------------------------------------------------------------------------

/**
 *  Oppgave 2C - Sjekker og returnerer om kjøpskode er korrekt.
 *
 *  @param    str  -  Tekst som skal sjekkes, og evt. få sitt 1.tegn endret
 *  @return   Om 'str' er en lovlig kjøpskode eller ei (på formen: Xnnn)
 */

bool Kunde::lovligKjop(string & str) const  {
  string lovlig("FHDR");              //  Forrett, Hovedrett, Dessert, dRikke.

  if (str.length() < 2) return false; //  Strengen er ikke lang nok.

  str[0] = toupper(str[0]);           //  Upcaser evt. 1.tegn.

  if (lovlig.find(str[0]) == string::npos)     //  1.tegn er ulovlig:
     return false;

  if (str[1] == '0') return false;             //  Kan IKKE starte med '0'.

  for (int i = 1;  i < str.length();  i++)     //  Består resten KUN av sifre?
      if (!isdigit(str[i]))  return false;

  return true;
}


/**
 *  Oppgave 2G - Leser ALLE data om EN kunde/ETT bord inn fra fil.
 *
 *  @param   inn  -  Filen det leses inn fra
 */
Kunde::Kunde(ifstream & inn)  {
  string hva;                            //  Koden for kjøpet/bestillingen.
  int ant,                               //  Antallet av en viss kode.
      totalt;                            //  Totalt antall kjøp/bestillinger.

  inn >> antall;  inn.ignore();          //  Leser antallet på bordet
  getline(inn, navn);                    //  Hovedkundens navn.
  inn >> totalt;                         //  Leser antall kjøp/bestillinger.
  for (int i = 0;  i < totalt;  i++)  {  //  Leser alle kjøpene/bestillingene.
      inn >> hva >> ant;
      kjop[hva] = ant;                   //  Legger inn i map'en.

  }
  inn.ignore();
}


/**
 *  Oppgave 2E - Finner og legger alle bestillinger/kjøp av en viss kategori
 *               inn i en referanse-overført map.
 *
 *  @param   best  -  Map med alle bestillinger med en viss kode
 */
void Kunde::bestillinger(const char kode, map <string, int> & best) const  {
  for (const auto & val : kjop)             //  Går igjennom alle kjøpene;
      if (val.first[0] == kode)             //  Kjøpet av aktuell kode.
          best[val.first] += val.second;    //  Kopierer over/plusser på.
}


/**
 *  Oppgave 2D - Registrerer nye kjøp på et bord/kunde inntil ulovlig skrives.
 *
 *  @see   lovligKjop(...)
 */
void Kunde::kjopForetas()  {
  string hva;
  int antall2, ant = 0;

  cout << "\n\tKjop av (F/H/D/Rxxx):  ";  getline(cin, hva);  //  Leser kode.
  while (lovligKjop(hva))  {                 //  Så lenge lovlig kode skrives:
    ant++;
    antall2 = lesInt("\t\tAntall", 0, antall);  //  Leser antallet.
    kjop[hva] += antall2;                       //  Legger inn/plusser på.
    cout << "\n\tKjop av (F/H/D/Rxxx):  ";  getline(cin, hva);  // Leser kode.
  }
  cout << "\n\tAntall nye kjop registrert:  " << ant << '\n';
}


/**
 *  Oppgave 2B - Leser inn 'Kunde' sine hoveddata.
 */
void Kunde::lesData()  {
  cout << "\tHovedgjestens navn:  ";   getline(cin, navn);
  antall = lesInt("\tAntall paa bordet", 1, ANTALL);
}


/**
 *  Oppgave 2A - Skriver ut ALT om 'Kunde' på skjermen.
 */
void Kunde::skrivData() const  {
  cout << "\n\tNavn: " << navn << "   ant.paa bordet: " << antall;
  cout << "\n\tHar hittil kjopt/bestilt:\n";
  if (kjop.size() > 0)
     for (const auto & val : kjop)            //  Skriver alt om alle kjøpene:
         cout << "\t\t" << val.first << "   antall: " << val.second << '\n';
  else
     cout << "\t\tIngenting\n";
}


/**
 *  Oppgave 2F - Skriver ALLE objektets data ut til fil.
 *
 *  @param   inn  -  Filen det skrives ut til
 */
void Kunde::skrivTilFil(ofstream & ut) const  {
  ut << antall << ' ' << navn << '\n' << kjop.size();
  for (const auto & val : kjop)                      //  Skriver alt om alle
      ut << ' ' << val.first << ' ' << val.second;   //    kjøpene:
  ut << '\n';
}


/**
 *  Oppgave 2F - Skriver objekt-typen ut til fil.
 *
 *  @param   inn  -  Filen det skrives ut til
 */
void Kunde::skrivTypeTilFil(ofstream & ut) const  {
  ut << "K ";
}


// ---------------------------------------------------------------------------

/**
 *  Oppgave 2G - Leser inn ALLE data om EN kunde/ETT bord fra fil.
 *
 *  @param   inn  -  Filen det leses inn fra
 */
Stamgjest::Stamgjest(ifstream & inn) : Kunde(inn)  {
  inn >> rabatt;     inn.ignore();
  getline(inn, service);
}


/**
 *  Oppgave 2B - Leser inn 'Stamgjest' sine data.
 */
void Stamgjest::lesData()  {
  Kunde::lesData();
  rabatt = lesInt("\tRabatt", 0, RABATT);
  cout << "\tBeskrivelse av service:  ";    getline(cin, service);
}


/**
 *  Oppgave 2A - Skriver ut ALT om 'Stamgjest' på skjermen.
 */
void Stamgjest::skrivData() const  {
  cout << "\n\tSTAMGJEST    med " << rabatt << "% i rabatt.\n";
  Kunde::skrivData();
  cout << "\tService:  " << service << '\n';
}


/**
 *  Oppgave 2F - Skriver ALLE objektets data ut til fil.
 *
 *  @param   inn  -  Filen det skrives ut til
 */
void Stamgjest::skrivTilFil(ofstream & ut) const  {
  Kunde::skrivTilFil(ut);
  ut << rabatt << ' ' << service << '\n';
}


/**
 *  Oppgave 2F - Skriver objekt-typen ut til fil.
 *
 *  @param   inn  -  Filen det skrives ut til
 */
void Stamgjest::skrivTypeTilFil(ofstream & ut) const  {
  ut << "S ";
}



// ---------------------------------------------------------------------------
//                       DEFINISJON AV ANDRE FUNKSJONER:
// ---------------------------------------------------------------------------

/**
 *  Oppgave 2A - Skriver ut ALT om EN kunde/ETT bord.
 *
 *  @see   Kunde::skrivData(),  Stamgjest::skrivData()
 */
void skrivEttBord()  {
  int nr = lesInt("\tVis alt om bordnummer", 1, ANTBORD);

  if (gBordene[nr-1])                    //  Bordet er i bruk:
     gBordene[nr-1]->skrivData();
  else
     cout << "\n\tBordet er for oyeblikket tomt!\n";
}


/**
 *  Oppgave 2B - Legger inn (om mulig) en ny kunde på et bord.
 *
 *  @see   Kunde::Kunde(),    Stamgjest::Stamgjest()
 *  @see   Kunde::lesData(),  Stamgjest::lesData()
 */
void nyKunde()  {
  int nr = lesInt("\tNy kunde paa bordnummer", 1, ANTBORD);
  Kunde* ny;
  char kundeType;

  if (!gBordene[nr-1])  {                   //  Bordet er IKKE i bruk:
     do  {                                  //  Leser i sikrer gjestetypen:
       kundeType = lesChar("\tStamgjest (J/N)");
     } while (kundeType != 'J'  &&  kundeType != 'N');
                                            //  Oppretter aktuell gjest/kunde:
     ny = (kundeType == 'N') ? new Kunde : new Stamgjest;
     ny->lesData();                         //  Dens data leses.
     gBordene[nr-1] = ny;                   //  Legges inn i datastrukturen.
     cout << "\n\tNy gjest/kunde registrert paa bord nr." << nr << '\n';
  } else
     cout << "\n\tBordet er opptatt!\n";
}


/**
 *  Oppgave 2D - Registrerer (om mulig) nye kjøp på et bord.
 *
 *  @see   Kunde::kjopForetas()
 *  @see   skrivTilFil()
 */
void kjop()  {
  int nr = lesInt("\tNytt/nye kjop paa bordnummer", 1, ANTBORD);

  if (gBordene[nr-1])  {                    //  Bordet er i bruk:
     gBordene[nr-1]->kjopForetas();         //  Registrerer kjøp/bestillinger.
     skrivTilFil();
  }else
     cout << "\n\tIngen paa dette bordet!  Korrekt bordnummer?\n";
}


/**
 *  Oppgave 2E - Finner og skriver alle nåværende aktuelle bestillinger/kjøp.
 *
 *  @see   Kunde::bestillinger()
 */
void bestillingerTotalt()  {
  string koder = "FHDR";                    //  Lovlige/aktuelle koder.
  map <string, int> bestillinger;           //  Alle bestillinger med EN kode.

  for (int i = 0;  i < koder.length();  i++)  { //  For hver kode:
      bestillinger.clear();                     //  Nullstiller bestillingene.
      for (int j = 0;  j < ANTBORD;  j++)       //  Går gjennom alle bordene:
          if (gBordene[j])                      //  Noen ved bordet:
             gBordene[j]->bestillinger(koder[i], bestillinger);  //  Registrerer.
      cout << "\n\tTotalt bestillinger/kjop akkurat naa i kategorien  "
           << koder[i] << ":\n";
      if (!bestillinger.empty())                //  Bestillinger med koden:
         for (const auto & val : bestillinger)
             cout << "\t\t" << val.first << "   totalt: " << val.second << '\n';
      else
         cout << "\t\tIngen\n";                 //  Stanser for hver kode:
      cout << "\t\t\t\t\tTrykk ENTER .....";   cin.ignore();
  }
}


/**
 *  Oppgave 2F - Skriver ALT om ALLE kundene/bordene til fil.
 *
 *  @see   Kunde::skrivTypeTilFil(...),   Stamgjest::skrivTypeTilFil(...)
 *  @see   Kunde::skrivTilFil(...),       Stamgjest::skrivTilFil(...)
 */
void skrivTilFil()  {
  ofstream utfil("EX_S26_RESTAURANT.DT2");

   for (int i = 0;  i < ANTBORD;  i++)  {        //  For hver kunde/bord:
       utfil << i+1 << ": ";
       if (gBordene[i])  {                       //  Bordet i bruk:
          gBordene[i]->skrivTypeTilFil(utfil);   //  Skriver 'K' eller 'S'.
          gBordene[i]->skrivTilFil(utfil);
       }  else
         utfil << "-\n";                         //  Ingen på/ved bordet.
   }
}


/**
 *  Oppgave 2G - Leser ALLE bordenes nåværende data/status inn fra fil.
 *
 *  @see   Kunde::Kunde(...),   Stamgjest::Stamgjest(...)
 */
void lesFraFil()  {
  ifstream innfil("EX_S26_RESTAURANT.DTA");
  char kundeType, ch;
  int nr;

  if (innfil)  {                                //  Filen finnes:
    cout << "\n\nLeser inn fra filen 'EX_S26_RESTAURANT.DTA' .....\n\n";
    for (int i = 0;  i < ANTBORD;  i++)  {       //  For hver kunde/bord:
        innfil >> nr >> ch >> kundeType;   innfil.ignore();
        if (kundeType != '-')                    //  Noen på/ved bordet:
           switch (kundeType)  {   //  Leser og legger inn aktuell kunde/bord:
             case 'K':  gBordene[nr-1] = new Kunde(innfil);             break;
             case 'S':  gBordene[nr-1] = new Stamgjest(innfil);         break;
             default:  cout << "\n\nUlovlig Kunde-type paa filen!\n\n"; break;
           }
    }
  } else                                        //  Filen finnes IKKE:
    cout << "\n\n\tFant ikke filen 'EX_S26_RESTAURANT.DTA'!\n\n";
}


/**
 *  Skriver programmets menyvalg/muligheter på skjermen.
 */
void skrivMeny()  {
  cout << "\nFolgende kommandoer er tilgjengelige:\n"
       << "   S  - Skriv ALT om ETT bord\n"
       << "   N  - Ny gjest/kunde ankommer\n"
       << "   K  - Kjop/bestillinger registreres\n"
       << "   B  - Bestillinger totalt for oyeblikket\n"
       << "   Q  - Quit / avslutt\n";
}
