/*
 * Contains tools using which you can easily create test-valuers
 */
#pragma once

#include <vector>
#include <string>
#include <sstream>

namespace NValuer {

using std::vector;
using std::string;
using std::istringstream;
using std::exception;

/*
 * Reads ejudge output and loads information about passed tests from it
 */
class TEjudgeCheckResultParser {
private:
    int N;
    vector<bool> passed;

public:

    TEjudgeCheckResultParser(FILE * input = stdin);
   
    size_t TestsNumber() const { return N; }
    bool Passed(size_t index) const;
};


/*
 * Represents classic group of tests notion
 */
class TestGroup {
public:

    enum EScoringType {
        AllOrNothing,       /// brings GroupScore point if and only if all tests of group are passed
        ClassicScore,       /// SetScore point uniformly distributes by all test, reminder gives +1 for tests with bigger id
        UniqueScore,        /// User specifies scores for all tests in group
    };

    size_t GetTestsAmount() const   { return FinishId - StartId + 1; }
    size_t GetStartId() const       { return StartId; }
    size_t GetFinishId() const      { return FinishId; }
    int GetGroupScore() const       { return GroupScore; }

    int GetTestScore(size_t index) const;
    int NormalizeScore(int score, size_t testsPassed) const; /// returns the real score

    bool Ok() const;            /// checks if all setting are correct

    TestGroup(EScoringType type = ClassicScore, size_t startId = 0, size_t finishId = 0, int groupScore = 0, const string& specScore = "");

private:
    EScoringType    Type;
    size_t          StartId;
    size_t          FinishId;
    int             GroupScore;
    int             AverageScore;
    size_t          ScoreReminder; /// always not negative
    vector<int>     Score;
};

/*
 * Takes tests group one by one,
 * checks compatibility of all groups,
 * calculates total score by given judge-result-parser
 */
class TValuer {
public:

    enum EValuerType {
        IgnoreOfflineGroups = 0,
        FullScore,
    };

    enum EGroupMode {
        Online = 0,
        Offline,
    };

    TValuer(EValuerType type = IgnoreOfflineGroups, int ignoreOfflineBorderScore = 0); /// if online score < <second arg>, ignore offline groups

    bool Add(const TestGroup& group, EGroupMode mode = Online);  /// returns Ok() of group, ingnores it if false

    bool Ok(int summaryScore = -1) const;  /// checks if all settings are correct,
                                           /// in case summatryScore >= 0 checks the sum of all test scores  to be equal to summaryScore

    template <typename TParser>
    int CalcScore(const TParser& parser) const;                 /// given parser, calcs score. If something wrong, returns -1

    size_t GroupsAmount() const             { return Group.size(); }
    size_t GetTestsAmount() const           { return TestsAmount; }
    void SetValuerType(EValuerType type)    { Type = type; }

private:
    EValuerType         Type;
    int                 IgnoreOfflineBorderScore;
    size_t              TestsAmount;
    vector<TestGroup>   Group;        /// warning, may be slow. Better use vector< std::tr1::shared_ptr<TestGroup> >
    vector<EGroupMode>  GroupMode;
};

/*
 * Implementation
 */

static inline void Message(const string& message) {
    fprintf(stderr, "%s\n", message.data());
}


int TestGroup::GetTestScore(size_t index) const
{
    if (index < GetStartId() || index > GetFinishId())
        return 0;
    switch (Type) {
        case AllOrNothing:
            return 0;
        case ClassicScore:
            return AverageScore + (GetFinishId() - index < ScoreReminder);
        case UniqueScore:
            return Score[index - GetStartId()];
        default:
            return 0;
    }
}

inline int TestGroup::NormalizeScore(int score, size_t testsPassed) const {
    if (Type == AllOrNothing)
        return (testsPassed == GetTestsAmount()) ? GetGroupScore() : 0;
    return score;
}

bool TestGroup::Ok() const
{
    if (StartId > FinishId)
        return false;
    if (Type != UniqueScore)
        return true;
    if (Type == UniqueScore && Score.size() != GetTestsAmount())
        return false;
    for (size_t i = 0; i < GetTestsAmount(); ++i) {
        if (Score[i] < 0) {
            Message("! warning: some of tests has negative score");
        }
    }
    return true;
}

extern "C" int atoi(const char *);

TestGroup::TestGroup(EScoringType type, size_t startId, size_t finishId, int groupScore, const string& specScore)
    : Type(type)
    , StartId(startId)
    , FinishId(finishId)
    , GroupScore(groupScore)
    , AverageScore(0)
    , ScoreReminder(0)
{
    if (Type == UniqueScore && StartId < FinishId) {
        try {
            string token;
            for (size_t i = 0; i < specScore.length(); ++i) {
                if (specScore[i] == ' ') {
                    if (!token.empty()) {
                        Score.push_back(atoi(token.data()));
                        token = "";
                    }
                } else {
                    token += specScore[i];
                }
            }
            if (!token.empty())
                Score.push_back(atoi(token.data()));

            if (Score.size() != GetTestsAmount())
                throw exception();

            int gscore = 0;
            for (size_t i = 0; i < GetTestsAmount(); ++i) {
                gscore += Score[i];
            }
            if (GroupScore < gscore)
                GroupScore = gscore;
        } catch (const exception &) {
            /// return to empty state
            Score.clear();
            StartId = FinishId = 0;
        }    
    } else if (GetTestsAmount()) {
        AverageScore = GetGroupScore() / static_cast<int>(GetTestsAmount());
        ScoreReminder = static_cast<size_t>(GetGroupScore() - AverageScore*static_cast<int>(GetTestsAmount()));
    }
}


TEjudgeCheckResultParser::TEjudgeCheckResultParser(FILE * input)
{
    try {
        fscanf(input, "%d", &N);
        if (N < 1) {
            Message("invalid amount of tests");
            throw exception();
        }
        
        passed.resize(N);
        for (int i = 0; i < N; ++i) {
            int x, y, z;
            if (fscanf(input, "%d%d%d", &x, &y, &z) != 3) {
                Message("can`t read check result");
                throw exception();
            }
            passed[i] = (x == 0);
        }
    } catch (const exception &) {
        /// report and return to empty state
        N = 0;
        passed.clear();
        fprintf(stderr, "! ejudge check result wasn`t read");
    }
}

inline bool TEjudgeCheckResultParser::Passed(size_t index) const {
    return (index >= 1 && static_cast<int>(index) <= N) ? passed[index - 1] : false;
}


TValuer::TValuer(EValuerType type, int ignoreOfflineBorderScore)
    : Type(type)
    , IgnoreOfflineBorderScore(ignoreOfflineBorderScore)
    , TestsAmount(0)
{}

bool TValuer::Add(const TestGroup& group, EGroupMode mode)
{
    if (!group.Ok()) {
        Message("! tried to add an incorrect test group, ignored");
        return false;
    }
    Group.push_back(group);
    GroupMode.push_back(mode);
    TestsAmount += group.GetTestsAmount();
    return true;
}

bool TValuer::Ok(int summaryScore) const
{
    vector<bool> used(GetTestsAmount());
    size_t tests = 0;
    int score = 0;

    for (size_t id = 0; id < GroupsAmount(); ++id) {
        for (size_t t = Group[id].GetStartId(); t <= Group[id].GetFinishId(); ++t) {
            if (t < 1 || t > GetTestsAmount())
                return false;
            if (used[t - 1])
                return false;
            used[t - 1] = true;
            ++tests;
        }
        score += Group[id].GetGroupScore();
    }
    if (tests != GetTestsAmount())
        return false;
    if (summaryScore >= 0 && summaryScore != score)
        return false;

    return true;
}

template <typename TParser>
int TValuer::CalcScore(const TParser& parser) const
{
    if (!Ok()) {
        Message("! unable to calculate the total score: tests set is broken");
        return -1;
    }

    int total = 0;
    /// first online only
    for (size_t id = 0; id < GroupsAmount(); ++id) {
        if (GroupMode[id] == Offline)
            continue;

        int gscore = 0;
        size_t passed = 0;
        for (size_t t = Group[id].GetStartId(); t <= Group[id].GetFinishId(); ++t) {
            if (parser.Passed(t)) {
                gscore += Group[id].GetTestScore(t);
                ++passed;
            }
        }

        total += Group[id].NormalizeScore(gscore, passed);
    }

    if (Type == IgnoreOfflineGroups || total < IgnoreOfflineBorderScore) {
        return total;
    }

    /// now offline only
    for (size_t id = 0; id < GroupsAmount(); ++id) {
        if (GroupMode[id] == Online)
            continue;

        int gscore = 0;
        size_t passed = 0;
        for (size_t t = Group[id].GetStartId(); t <= Group[id].GetFinishId(); ++t) {
            if (parser.Passed(t)) {
                gscore += Group[id].GetTestScore(t);
                ++passed;
            }
        }

        total += Group[id].NormalizeScore(gscore, passed);
    }


    return total;
}

} /// namespace NValuer ends here
