#include "user_configuration.h"

#ifdef _USE_NAMESPACES
    namespace Else { 
#endif // _USE_NAMESPACES

void
UserConfiguration::readSystemName(const string& filename, conform_string& systemName)
{
    systemName.clear();
    fstream cnf(filename.c_str(), ios::in);
    if (cnf)
    {
        cnf.ignore(SKIP_UNTIL_EOF, ':');
        cnf >> ws >> systemName;
        cnf.close();
    }
}

UserConfiguration::UserConfiguration() { }

const string&
UserConfiguration::nickname() const { return TOOLS_NICKNAME; }
    
const unsigned char&
UserConfiguration::id() const { return TOOLS_ID; }

bool
UserConfiguration::readFrom(const string& filename, const ConfigType mode, const bool quiet)
{   
    Natural leftP;  // Loc. of the begining of the separator's id (for display/edit errors)
    const ShortNatural SEP_MAX(6);
    bool  defined[6];                // To know which separators are defined.
    for (ShortNatural i(0); i < SEP_MAX; ++i) defined[i] = false;

    // If the file not exist, the state of the instance not change.
    fstream config(filename.c_str(), ios::in);
    if (!config)
    {
        if (!quiet) msg.message(nickname(),
                                string("Unable to open the configuration file '") + filename + string("'."),
                                STANDARD_MSG);
        return false;
    } 

    // Clear the internal attribut, even the file has bad format.
    this->reference = (mode == Reference);
    this->fileName = filename;
    this->systemName.clear();
    this->taggedCorpus.clear();
    this->mapName.clear();
    this->mapMode = (reference ? DirectTbl : UnknownTbl);
    this->unknownTag = "UNKNOWN"; //(*)
    this->irrelevantTag.clear();
    this->neutralizedTag = "^"; // (*)
    this->unalignedTag.clear();

    config.ignore(SKIP_UNTIL_EOF, ':');
    if (! (config >> systemName >> ws))
    {
        if (!quiet) msg.error(id(), 0, 0,
                              "Corrupted file",
                              "Configuration file does not contain valid datas (System's name).",
                              Location(&filename, 0, config.tellg()));
        return false;
    }
    for (ShortNatural i(0); i < SEP_MAX; ++i) 
    {
        bool ok(true);
        char ch;          // Separators's characters, readed chr by chr.
        Natural sz;       // Number of chars to read
        Natural sep;      // Separators's id

        leftP = config.tellg();
        config.ignore(SKIP_UNTIL_EOF, 'P');    // skip 'SEP'
        if ( ! (config >> sep))
        {
            ostringstream os;
            os << "The #" << (i+1) << "st definition (SEP" << (sep+1) <<") is not valid.";
            msg.error(id(), 0, 0,
                      string("Corrupted file"),
                      os.str(),
                      Location(&filename, leftP, config.tellg()));
            return false;
        }
        if ((sep < 1) || ( sep > SEP_MAX))
        {
            // Search a undefined separator
            ShortNatural probableSep(0);
            while (defined[probableSep]) ++probableSep;
            ++probableSep;  // okay... +,-,+,- not cleany...
                    
            ostringstream os;
            os << "Entry 'SEP" << sep << "' not in range [SEP1..SEP" << SEP_MAX 
               << "]. Assuming that is SEP" << probableSep << '.';
            msg.error(id(), 1, 0,
                      string("Invalid separator id"),
                      os.str(),
                      Location(&filename, leftP, config.tellg()));
            sep = probableSep;
        } 
        else if (sep != (i+1))
        {
            ostringstream os;
            os << "Waited for 'SEP" << (i+1) << "', not for 'SEP" << (sep+1) << "'.";
            msg.warning(id(), 1, 1,
                        string("Separator not in sequence"),
                        os.str(),
                        Location(&filename, leftP, config.tellg()));
        }
        if (defined[sep-1])
        {
           ostringstream os;
           ShortNatural probableSep(0);
           while (defined[probableSep]) ++probableSep;
           ++probableSep;
           os << "The separator 'SEP" << (sep+1) << "' is already defined. "
              << "Assuming that is SEP" << probableSep << '.';
           msg.error(id(), 1, 2,
                     string("Separator redefinition"),
                     os.str(),
                     Location(&filename, leftP, config.tellg()));
       }
       defined[--sep] = true;
       separator[sep].first.clear();
       separator[sep].second.clear();
       config.ignore(SKIP_UNTIL_EOF,'=');     // skip ':size='
       leftP = config.tellg();
       if (!(config >> sz))
       {
            ostringstream os;
            os << "Error when reading the size of the #" << i+1 << "st separator (SEP" << (sep+1) << ").";
            msg.error(id(), 0, 0,
                      string("Corrupted file"),
                      os.str(),
                      Location(&filename, leftP, config.tellg()));
            return false;
       }
       if ((sz < 1) || (sz > 250))
       {
			ostringstream os;
            os << "Size of the separator 'SEP" << (sep-1) << "', (size = " << sz << ") is not valide. Assume 1";
            msg.error(id(), 2, 1,
                      string("Invalid size"),
                      os.str(),
                      Location(&filename, leftP, config.tellg()));
            sz = 1;
        }           
        leftP = config.tellg();
        config.ignore(SKIP_UNTIL_EOF,'=');     // skip ' value='
        for (; sz>0; --sz)
        {
            config.get(ch);
            separator[sep].first.push_back(ch);
        }
		if (config.fail())
		{
			ostringstream os;
			os << "Error occurs when reading the value of SEP" << (sep+1) << '.';
			msg.error(id(), 0 , 0,
					  string("Corrupted file"),
                      os.str(),
                      Location(&filename, leftP, config.tellg()));
            return false;
        }
        config >> ws;
        if (config.peek() == 't')
        {
            leftP = config.tellg();
            if ((sep == 3) || (sep == 5))
            {
                msg.error(id(), 1, 3,
                          "Prohibed part",
                          "Separator 4 and 6 do not admit transcoding!",
                          Location(&filename, leftP));
            }
            config.ignore(SKIP_UNTIL_EOF, '=');  // skip 'transcoded='
            if (! (config >> separator[sep].second))
            {
                ostringstream os;
                os << "Error when reading the transcoded part of SEP" << (sep+1) << '.';
                msg.error(id(), 0, 0,
                          string("Corrupted file"),
                          os.str(),
                          Location(&filename, leftP, config.tellg()));
                return false;
            }
            config >> ws;
        }
    }
    leftP = config.tellg();
    if (mode == Unknown)
        reference = (config.peek() == 'I');
           
    if (reference)
    {
        if (config.peek() != 'I')
            msg.error(id(), 4, 1,
                      "Missing entry",
                      "Missing 'irrelevant' (IRRV) definition",
                      Location(&filename, leftP));
        else
        {
            config.ignore(SKIP_UNTIL_EOF, ':');
            config >> irrelevantTag >> ws;
        }
        leftP = config.tellg();
        if (config.peek() != 'N')
            msg.error(id(), 4, 1,
                      "Missing entry",
                      "Missing 'neutralized' (NTRL) definition",
                      Location(&filename, leftP));
        else
        {
            config.ignore(SKIP_UNTIL_EOF, ':');
            config >> neutralizedTag >> ws;
        }
        leftP = config.tellg();
        if (config.peek() != 'A')
            msg.error(id(), 4, 1,
                      "Missing entry",
                      "Missing 'Unaligned' (ALGN) definition",
                      Location(&filename, leftP));
        else
        {
            config.ignore(SKIP_UNTIL_EOF, ':');
            config >> unalignedTag >> ws;
        }
        if (neutralizedTag == irrelevantTag)
            msg.warning(id(), 4, 2,
                        string("Identical Sequence"),
                        string("Neutralized & irrelevant sequences are the same (")
                            + irrelevantTag + string(")."),
                        Location(&filename, leftP));
    }
    else
    {
        if (config.peek() != 'U')
            msg.error(id(), 4, 1,
                      "Missing entry",
                      "Missing 'unknown' (UNKN) definition",
                      Location(&filename, leftP));
        else
        {
            config.ignore(SKIP_UNTIL_EOF, ':');
            config >> unknownTag >> ws;
        }
    }
    leftP = config.tellg();
    if (config.peek() != 'T')
        msg.error(id(), 3, 0,
                  "Invalid Entry",
                  "TagsMap (defined by 'TMAP') waited.",
                  Location(&filename, leftP));
    else
    {
        config.ignore(SKIP_UNTIL_EOF, ':');
        config >> mapName;
    }
    leftP = config.tellg();
    conform_string add;
    config >> add;
    if (add.find("type=") == 0) 
    {
        switch (add[5]) 
        {
            case 'd' :
            case 'D' : mapMode = DirectTbl; break;
            case 'r' :
            case 'R' : mapMode = ReverseTbl; break;
            default  : msg.error(id(), 3, 1,
                                 "Invalid map's type",
                                 "Type can be 'direct' or 'reverse'",
                                 Location(&filename, leftP, leftP+add.size()));
                       break;
        }
        add.clear();
    }
    while (config.good()) 
    {
        conform_string corpus;
        conform_string tagged;
        Natural leftP(config.tellg());
        if (add.empty()) config >> corpus >> tagged;
        else { corpus = add; add.clear(); config >> tagged; }
        if (corpus.empty() || tagged.empty())
            msg.warning(id(), 2, 0,
                        string("Corpus list with empty entry"),
                        string("The corpus mapping list ('") + corpus + string("' -> '")
                            + tagged + string("') with empty element will be igored."),
                        Location(&filename, leftP, config.tellg()));
        else taggedCorpus[corpus] = tagged;
    }
    config.close();
    if ( (separator[2].first == separator[0].first) || (separator[2].first == separator[1].first) )
    {
        msg.error(id(), 3, 0,
                  string("Restriction violation"),
                  string("Restriction: SEP3 <> {SEP1,SEP2} dissatisfied (") + separator[2].first
                      + string(" == {") + separator[0].first + string(" or ") + separator[1].first 
                      + string("})."),
                  Location(&filename, 0));
        return false;
    }
    return true;
}

const conform_string&
UserConfiguration::system() const { return systemName; }

const conform_string&
UserConfiguration::operator()(const ShortNatural index, const bool transcoded = false) const
{
    assert(index<6);
    return (transcoded ? separator[index].second : separator[index].first);
}

conform_string
UserConfiguration::sep(const ShortNatural index, const bool transcoded = false) const
{
	assert(index<6);
    return (*this)(index,transcoded);
}


const string UserConfiguration::TOOLS_NICKNAME("ConfigurationFile");
const unsigned char UserConfiguration::TOOLS_ID('C');

#ifdef _USE_NAMESPACES
}
#endif // _USE_NAMESPACES

