
//
// To jest przykładowy kod z podrozdziału 10.11.2 „Odczytywanie struktur wartości” książki 
// "Programowanie. Teoria i praktyka z wykorzystaniem C++" Bjarne'a Stroustrupa.
//

#include "std_lib_facilities.h"

//------------------------------------------------------------------------------

const int not_a_reading = -7777;    // Mniej niż zero bezwzględne.
const int not_a_month = -1;

//------------------------------------------------------------------------------

struct Day {
    vector<double> hour;
    Day();    // Inicjacja godzin wartością „nieodczyt”.
};

//------------------------------------------------------------------------------

Day::Day() : hour(24)
{
    for (int i = 0; i<hour.size(); ++i) hour[i]=not_a_reading;
}

//------------------------------------------------------------------------------

struct Month {        // Miesiąc pomiarów temperatury.
    int month;        // <0,11>, styczeń == 0.
    vector<Day> day;  // <1,31>, jeden wektor odczytów na dzień.
    Month()           // W miesiącu może być najwyżej 31 dni (zmarnowany element day[0]).
        :month(not_a_month), day(32) { }
};

//------------------------------------------------------------------------------

struct Year {             // Rok wyników pomiarów temperatury, zorganizowany według miesięcy.
    int year;             // Liczba dodatnia == rok.
    vector<Month> month;  // <0,11>, styczeń == 0.
    Year() :month(12) { } // W roku jest 12 miesięcy.
};

//------------------------------------------------------------------------------

struct Reading {
    int day;
    int hour;
    double temperature;
};

//------------------------------------------------------------------------------

int month_to_int(string s);
bool is_valid(const Reading& r);
void end_of_loop(istream& ist, char term, const string& message);

//------------------------------------------------------------------------------

istream& operator>>(istream& is, Reading& r)
// Wczytuje wynik pomiaru temperatury z is do r.
// format: ( 3 4 9.7 )
// Sprawdza format, ale nie sprawdza poprawności danych.
{
    char ch1;
    if (is>>ch1 && ch1!='(') {    // Czy to może być Reading?
        is.unget();
        is.clear(ios_base::failbit);
        return is;
    }

    char ch2;
    int d;
    int h;
    double t;
    is >> d >> h >> t >> ch2;
    if (!is || ch2!=')') error("Nieprawidłowy odczyt."); // Problemy z odczytem.
    r.day = d;
    r.hour = h;
    r.temperature = t;
    return is;
}

//------------------------------------------------------------------------------

istream& operator>>(istream& is, Month& m)
// Wczytuje miesiąc z is do m.
// format: { month feb ... }
{
    char ch = 0;
    if (is >> ch && ch!='{') {
        is.unget();
        is.clear(ios_base::failbit);    // Nie udało się wczytać miesiąca.
        return is;
    }

    string month_marker;
    string mm;    
    is >> month_marker >> mm;
    if (!is || month_marker!="month") error("Niepoprawny początek miesiąca.");
    m.month = month_to_int(mm);

    Reading r;
    int no_of_duplicate_readings = 0;
    int no_invalid_readings = 0;

    while (is >> r)
        if (is_valid(r)) {
            if (m.day[r.day].hour[r.hour] != not_a_reading)
                ++no_of_duplicate_readings;
            m.day[r.day].hour[r.hour] = r.temperature;
        }
        else
            ++no_invalid_readings;
    end_of_loop(is,'}',"Niepoprawny koniec miesiąca.");
    return is;
}

//------------------------------------------------------------------------------

const int implausible_min = -200;
const int implausible_max = 200;

bool is_valid(const Reading& r)
// Szybki test.
{
    if (r.day<1 || 31<r.day) return false;
    if (r.hour<0 || 23<r.hour) return false;
    if (r.temperature<implausible_min|| implausible_max<r.temperature)
        return false;
    return true;
}

//------------------------------------------------------------------------------

istream& operator>>(istream& is, Year& y)
// Wczytuje rok z is do y.
// format: { year 1972 … }
{
    char ch;
    is >> ch;
    if (ch!='{') {
        is.unget();
        is.clear(ios::failbit);
        return is;
    }

    string year_marker;
    int yy;
    is >> year_marker >> yy;
    if (!is || year_marker!="year") error("Niepoprawny początek roku.");
    y.year = yy;

    while(true) {
        Month m;    // W każdym cyklu tworzy czystą zmienną m.
        if(!(is >> m)) break;
        y.month[m.month] = m;
    }

    end_of_loop(is,'}',"Niepoprawne zakończenie roku.");
    return is;
}

//------------------------------------------------------------------------------

void end_of_loop(istream& ist, char term, const string& message)
{
    if (ist.fail()) { // Używa term jako znaku końca i/lub znaku oddzielającego.
        ist.clear();
        char ch;
        if (ist>>ch && ch==term) return;    // Wszystko w porządku.
        error(message);
    }
}

//------------------------------------------------------------------------------

vector<string> month_input_tbl;    // month_input_tbl[0]=="jan"

void init_input_tbl(vector<string>& tbl)
// Inicjacja wektora reprezentacji danych wejściowych.
    tbl.push_back("jan");
    tbl.push_back("feb");
    tbl.push_back("mar");
    tbl.push_back("apr");
    tbl.push_back("may");
    tbl.push_back("jun");
    tbl.push_back("jul");
    tbl.push_back("aug");
    tbl.push_back("sep");
    tbl.push_back("oct");
    tbl.push_back("nov");
    tbl.push_back("dec");
}

//------------------------------------------------------------------------------

int month_to_int(string s)
// Czy s jest nazwą miesiąca? Jeśli tak, zwraca jej indeks z zakresu <0,11>, w przeciwnym przypadku zwraca –1.
{
    for (int i=0; i<12; ++i) if (month_input_tbl[i]==s) return i;
    return -1;
}

//------------------------------------------------------------------------------

vector<string> month_print_tbl;    // month_print_tbl[0]=="January"

void init_print_tbl(vector<string>& tbl)
// Inicjacja wektora reprezentacji wyjściowych.
{
    tbl.push_back("Styczeń");
    tbl.push_back("Luty");
    tbl.push_back("Marzec");
    tbl.push_back("Kwiecień");
    tbl.push_back("Maj");
    tbl.push_back("Czerwiec");
    tbl.push_back("Lipiec");
    tbl.push_back("Sierpień");
    tbl.push_back("Wrzesień");
    tbl.push_back("Październik");
    tbl.push_back("Listopad");
    tbl.push_back("Grudzień");
}

//------------------------------------------------------------------------------

string int_to_month(int i)
// Zakres numerów miesięcy: [0:11].
{
    if (i<0 || 12<=i) error("Nieprawidłowy indeks miesiąca.");
    return month_print_tbl[i];
}

//------------------------------------------------------------------------------

void print_year(ostream& ost, const Year& y)
{
    ost << y.year << ' ';
}

//------------------------------------------------------------------------------

int main()
try
{
    // Inicjacja tablic reprezentacji:
    init_print_tbl(month_print_tbl);
    init_input_tbl(month_input_tbl);

    // Otwarcie pliku wejściowego.
    cout << "Podaj nazwę pliku z danymi.\n";
    string name;
    cin >> name;
    ifstream ifs(name.c_str());
    if (!ifs) error("Nie można otworzyć pliku.",name);

    ifs.exceptions(ifs.exceptions()|ios_base::badbit);    // Zgłasza dla bad().

    // Otwiera plik wyjściowy:
    cout << "Podaj nazwę pliku wyjściowego:\n";
    cin >> name;
    ofstream ofs(name.c_str());
    if (!ofs) error("Nie można otworzyć pliku wyjściowego.",name);

    // Wczytuje dowolną liczbę lat:
    vector<Year> ys;
    while(true) {    
        Year y;        // W każdym cyklu tworzy nowy rok.
        if (!(ifs>>y)) break;
        ys.push_back(y);
    }
    cout << "Wczytano wyniki pomiarów z " << ys.size() << " lat.\n";

    for (int i = 0; i<ys.size(); ++i) print_year(ofs,ys[i]);
}
catch (exception& e) {
    cerr << "Błąd: " << e.what() << '\n'; 
    return 1;
}
catch (...) {
    cerr << "Nieznany wyjątek!\n"; 
    return 2;
}

//------------------------------------------------------------------------------
