// $Id: ConverterUI.cpp,v 1.81 2018/01/03 21:32:56 mkkuhner Exp $

/*
Copyright 2002-2005 Mary K. Kuhner, Peter Beerli, and Joseph Felsenstein

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/

/* Authors: Patrick Colacurcio, Peter Beerli, Mary Kuhner, Jon Yamato 
/* Authors: Patrick Colacurcio, Peter Beerli, Mary Kuhner, Jon Yamato 
and Joseph Felsenstein */


// This file contains the UI and the main() function for the converter program

// extensive revisions to allow UI and filereading to interact.
// Jon 2002/03/21 - 2002/?

#include <cassert>
#include <iostream>
#include <fstream>
#include <map>
#include <algorithm>
#include <iterator>   // for ostream_iterator

#include "Converter_ConverterUI.h"
#include "Converter_PhylipConverter.h"
#include "Converter_MigrateConverter.h"
#include "Converter_LamarcDS.h"
#include "Converter_DataSourceException.h"
#include "Converter_SpacingDS.h"
#include "Converter_HapConverter.h"
#include "Converter_UserFileUtil.h"
#include "Converter_SpaceConverter.h"
#include "random.h"
#include "constants.h" // for #define MENU
                       //     for datatype const strings (DNA, SNP, etc)
#include "stringx.h"   // for FromString() and IsInteger()
#include "Converter_types.h" // for map typedefs

#include "nomenuglobals.h" // to handle nomenu case

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

const long DEFAULTLONG = 0;
const long CONVUI_LINELENGTH = 70;
const long CONVUI_ERRORINDENT = 6;

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

bool isInterleaved()
{
    string msg = "Is this file in the interleaved format?";

    return GetYesOrNo(msg,false);

}

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

string getFileName(const UserFileUtil& fileutil)
{
    string buffer;
    long tries = 0;
    while (tries < 3)
    {
        buffer.erase();
        cout << "Enter the file name: ";
        getline(cin, buffer);

        // check to see if file exists
        if (fileutil.IsFilePresent(buffer)) break;
        else
        {
            cout << "\nCouldn't find file: " << buffer << endl;
            ++tries;
        }
    }

    return buffer;
}

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

string getHapFileName(const string& regionname,
                      const UserFileUtil& fileutil)
{
#ifndef JSIM
    string fname;
    string msg = "\nHave you prepared a file containing phase information";
    msg += " for the region " + regionname + "?";

    if (GetYesOrNo(msg,false))
    {
        fname = getFileName(fileutil);
    }

    return fname;
#else
    return convstr::HAPFILENAME;
#endif
}

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

string getFormat()
{
    bool success = false;
    string buffer;

    while (!success)
    {
        cout << "Please enter:" << endl <<  "(1) if this is a Phylip format file" << endl
             << "(2) if this is a Migrate/Recombine format file" << endl;
        getline(cin, buffer);

        if ((buffer == "1"))
        {
            return "Phylip";
        }
        else if (buffer == "2")
        {
            return "Migrate";
        }
        else
        {
            cout << "Enter either '1' or '2'." << endl;
        }
    }

    return buffer; // shouldn't be able to get here
}

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

string getDataType()
{
#ifndef JSIM
    bool success = false;
    string buffer;
    cout << "What is the datatype of your data set?" << endl;
    while (!success)
    {
        cout << "Please enter:" << endl << "(1) if it is DNA" << endl;
        cout << "(2) if it is SNP\n" << "(3) if it is MICROSAT" << endl ;
        cout << "(4) if it is K-allele (including electrophoretic)" << endl;
        getline(cin, buffer);
        if ((buffer == "1"))
        {
            return lamarcstrings::DNA;
        }
        else if (buffer == "2")
        {
            return lamarcstrings::SNP;
        }
        else if (buffer == "3")
        {
            return lamarcstrings::MICROSAT;
        }
        else if (buffer == "4")
        {
            return lamarcstrings::KALLELE;
        }
        else
        {
            cout << "Enter a number between 1 and 4." << endl;
        }
    }

#else
    return convstr::GENETICDATATYPE;
#endif

    return string("Can't be here\n"); // shouldn't be able to get here

}

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

long getLong(const string& name, bool mustbepositive)
{
    long value;
    bool success = false;
    while (!success)
    {
        string buffer;
        getline(cin,buffer);
        if (IsInteger(buffer))
        {
            success = true;
            FromString(buffer,value);
            if (mustbepositive && value < 0)
            {
                success = false;
                cout << "The "  + name + " must be a positive integer";
                cout << endl;
            }
        }
        else cout << "The " + name + " must be an integer" << endl;
    }

    return value;
}

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

long getRegionalMapInfo(const string& regionName, long& length,
                        long& offset)
{
#ifndef JSIM
    bool mustbepositive = true;
    cout << "What is the total length of the region, " + regionName + "?";
    cout << endl;
    length = getLong(string("length"),mustbepositive);

    mustbepositive = false;
    cout << "What position number does the region start at?" << endl;
    offset = getLong(string("distance"));

    // this question is incorrectly placed, it should be asking about
    // loci, not regions.  In any case, it is only relevant once loci
    // come into use.
    // cout << "What is the map position of the beginning of the region?";
    // cout << endl;

    // return getLong(string("position"));
    return 0L;

#else
    length = convstr::REGIONLENGTH;
    offset = convstr::REGIONOFFSET;
    return convstr::LOCUSMAPPOS;

#endif
}

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

string GetMapFileName()
{
#ifndef JSIM
    string fname;

    string msg = "\nHave you prepared a file mapping your genetic ";
    msg += "markers to positions\non the sequence?";

    if (GetYesOrNo(msg,false))
    {
        UserFileUtil fileutil;
        fname = getFileName(fileutil);
    }

    return fname;
#else
    return convstr::MAPFILENAME;
#endif

} // GetMapFileName

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

void SetRegionLength(RegionMap::iterator region)
{
    long retries = 0;
    while(retries < 3)
    {
        cout << "What is the total length (including ";
        cout << "non-polymorphic positions) of the\nsequenced ";
        cout << "region " + region->first + "?" << endl;
        string buffer;
        getline(cin,buffer);
        long length;
        FromString(buffer,length);
        long nmarkers = region->second.getNmarkers();
        if (length >= nmarkers)
        {
            SpacingDS spacing(length, nmarkers);
            region->second.setSpacing(spacing);
            break;
        }
        else
        {
            cout << "Total length of region must be at least as ";
            cout << "large as the number of SNPs.\n\n";
            ++retries;
        }
    }
} // SetRegionLength

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

void SetMapInfo(LamarcDS& dataStore, const string& fname)
{
    if (fname.empty())
    {
        RegionMap::iterator region;
        for(region = dataStore.getFirstRegion();
            region != dataStore.getLastRegion(); // this is really foo.end()
            ++region)
        {
            if (region->second.HasSNPs())
                SetRegionLength(region);
        }
        return;
    }

    SpaceConverter converter(dataStore);

    SpaceMap spaces = converter.ReadSpacingInfo(fname);

    // for each region, interact with user to get rest of info, then build
    // and add the appropiate SpacingDS.
    RegionMap::iterator region;
    for(region = dataStore.getFirstRegion();
        region != dataStore.getLastRegion(); // this is really foo.end()
        ++region)
    {
        // if spacing info was provided by the user
        if (spaces.find(region->second.getName()) != spaces.end())
        {
            long length, offset, mapposition, retries = 0;
            while(retries < 3)
            {
                mapposition = getRegionalMapInfo(region->first,length,offset);
                try
                {
                    SpacingDS spacing(spaces[region->first], length, offset,
                                      mapposition, region->second.getNmarkers());
                    region->second.setSpacing(spacing);
                    break;
                }
                catch (InconsistentDataError& e)
                {
                    cout << e.type() << ": " << e.what() << endl;
                    ++retries;
                }
            }
        }
        else
        {
            // else if the region has SNPs then query/set the region's length
            if (region->second.HasSNPs())
                SetRegionLength(region);
        }
    }
} // SetMapInfo

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

void SetHapInfo(LamarcDS& dataStore)
{
    RegionMap::iterator regionIt;
    for (regionIt = dataStore.getFirstRegion();
         regionIt != dataStore.getLastRegion(); ++regionIt)
    {
        long attempt, maxtries = 3;
        for(attempt = 0; attempt < maxtries; ++attempt)
        {
            try
            {
                UserFileUtil fileutil;
                string hapfilename =
                    getHapFileName(regionIt->first,fileutil);

                if (hapfilename.empty()) break;

                Random randomgenerator; // uses system clock
                HapConverter hapconverter(regionIt->second,randomgenerator);

                vector<IndividualDS> individuals =
                    hapconverter.ReadHapInfo(hapfilename);

                hapconverter.ReplaceIndividualsWith(individuals);
#ifndef JSIM
                cout << "Set phase information for region ";
                cout << regionIt->first << endl << endl;
#endif
                break;
            }
            catch(FileFormatError& e)
            {
                cout << e.type() << ": " << e.what() << endl;
                if (attempt == maxtries-1)
                {
                    cout << "\n\nExceeded retry threshold\n";
                    cout << "Attempt at conversion failed\n\n";
                    exit(0);
                }
                else
                    continue;
            }
        }
    }
} // SetHapInfo

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

bool AreMicrosRegions()
{
#ifndef JSIM
    string query = "Should each microsat marker be treated as an ";
    query += "independent region?";

    return GetYesOrNo(query,true);
#else
    return convstr::MICROREGIONS;
#endif

} // AreMicrosRegions

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

bool GetYesOrNo(const string& query, bool defaultyes)
{
    cout << query;
    if (defaultyes)
        cout << "  (y)/n." << endl;
    else
        cout << "  y/(n)." << endl;

    while(true)
    {
        string buffer;
        getline(cin, buffer);

        if (buffer == "" && defaultyes)
            return true;
        if (buffer == "" && !defaultyes)
            return false;

        if (ciStringEqual(buffer, string("y")) || ciStringEqual(buffer, string("yes")))
        {
            return true;
        }

        if (ciStringEqual(buffer, string("n")) ||
            ciStringEqual(buffer, string("no")))
        {
            return false;
        }

        cout << "You entered: " << buffer << endl;
        cout << "Please enter 'y' or 'n'." << endl;
    }

    assert(false);
    return false;
} // GetYesOrNo

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

void Unit::DisplayUnit() const
{
    if (region != "") cout << MakeCentered(region,16);
    else
    {
        string unknownpop = string("Unknown") + ToString(regionno);
        cout << MakeCentered(unknownpop,16);
    }
    if (population != "") cout << MakeCentered(population,16);
    else cout << MakeCentered("Unknown",16);
    cout << MakeCentered(ToString(tips),6) <<
        MakeCentered(ToString(markers),10) <<
        MakeCentered(datatype,10) <<
        MakeCentered(filename,20) << endl;

} // DisplayUnit

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

bool Unit::operator<(const Unit& other) const
{
    if (region < other.region) return true;
    if (region > other.region) return false;

    if (population < other.population) return true;
    return false;  // identical Units compare false by flowthrough here

} // operator<

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

bool Unit::IsIn(const string& regionname, const string& popname) const
{
    return (ciStringEqual(regionname, region) && ciStringEqual(popname, population));
} // Unit::IsIn

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

set<string> PopRegionRelation::GetRegionNames() const
{
    set<string> regionnames;
    vector<Unit>::const_iterator unit;
    for(unit = units.begin(); unit != units.end(); ++unit)
        regionnames.insert(unit->region);

    return regionnames;

} // PopRegionRelation::GetRegionNames()

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

set<string> PopRegionRelation::GetPopulationNames() const
{
    set<string> popnames;
    vector<Unit>::const_iterator unit;
    for(unit = units.begin(); unit != units.end(); ++unit)
        popnames.insert(unit->population);

    return popnames;

} // PopRegionRelation::GetPopulationNames()

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

void PopRegionRelation::DisplayPopRegionRelation()
{
    unsigned long i;
    cout << MakeCentered("Unit",4) << "  " <<
        MakeCentered("Region",16)  <<
        MakeCentered("Population",16) <<
        MakeCentered("Tips",6) <<
        MakeCentered("Markers",10) <<
        MakeCentered("Datatype",10) <<
        MakeCentered("Filename",20) << endl;
    for (i = 0; i < units.size(); ++i)
    {
        cout << MakeJustified(indexToKey(i),4) << "  ";
        units[i].DisplayUnit();
    }
    cout << endl;

} // DisplayPopRegionRelation

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

void PopRegionRelation::ChangeRegions()
{
    cout << "***Grouping units into regions" << endl;
    while (true)
    {
        while (true)
        {
            cout << "List one or more units that belong to the same region" << endl;
            cout << "Return/Enter on an empty line to assign populations" << endl;
            string regionlist;
            getline(cin, regionlist);
            if (regionlist == "") break;
            // get region numbers out of regionlist
            vector<long> regions;
            FromString(regionlist, regions);
            // make sure that the user type legal values 1..#number of regions
            if(vec_greater(regions,static_cast<long>(units.size())) ||
               (vec_leq(regions, 0L)))
            {
                cout << "Please enter a set of valid region numbers" << endl;
                continue;
            }
            cout << "Name of this region?" << endl;
            string region;
            getline(cin, region);
            unsigned long i;
            for (i = 0; i < regions.size(); ++i)
            {
                if (regions[i] < 1 || regions[i] > static_cast<long>(units.size()))
                    throw FileFormatError(string("Bad region!"));
                else
                {
                    units[regions[i]-1].region = region;
                }
            }
            DisplayPopRegionRelation();
        }
        if (RegionsValid()) return;
        else
        {
            string msg("Not all regions are valid; try again.\n\n");
            cout << MakeIndent(msg,CONVUI_ERRORINDENT) << endl;
        }
    }

} // ChangeRegions

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

void PopRegionRelation::ChangePopulations()
{
    cout << "\n***Grouping units into populations" << endl;
    while (true)
    {
        while (true)
        {
            cout << "List one or more units that belong to the same ";
            cout << "population" << endl << "Return/Enter on an empty line ";
            cout << "to finish all assignments" << endl;
            string populationlist;
            getline(cin, populationlist);
            if (populationlist == "") break;
            // get population numbers out of populationlist
            vector<long> populations;
            FromString(populationlist, populations);
            // make sure that the user type legal values 1..#number of populations
            if(vec_greater(populations,static_cast<long>(units.size())) ||
               (vec_leq(populations, 0L)))
            {
                cout << "Please enter a set of valid population numbers" << endl;
                continue;
            }
            cout << "Name of this population?" << endl;
            string population;
            getline(cin, population);
            unsigned long i;
            for (i = 0; i < populations.size(); ++i)
            {
                if (populations[i] < 1 || populations[i] > static_cast<long>(units.size()))
                    throw FileFormatError(string("Bad population!"));
                else
                {
                    units[populations[i]-1].population = population;
                }
            }
            DisplayPopRegionRelation();
        }
        if (PopsValid()) return;
        else
        {
            string msg("Not all populations are valid; try again.\n\n");
            cout << MakeIndent(msg,CONVUI_ERRORINDENT) << endl;
        }
    }
} // ChangePopulations

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

bool PopRegionRelation::RegionsValid() const
{
    unsigned long i;
    MarkerMap markermap;
    MarkerMap::iterator markit;
    TypeMap typemap;
    TypeMap::iterator typeit;

    for (i = 0; i < units.size(); ++i)
    {
        const Unit& unit = units[i];
        if (unit.region == "")
        {
            cout << "Empty region name" << endl;
            return false;
        }

        // check if each region has a consistent number of markers, using a map
        markit = markermap.find(unit.region);
        if (markit == markermap.end())
            markermap.insert(make_pair(unit.region,unit.markers));
        else
        {
            if ((*markit).second != unit.markers)
            {
                string msg("\nCurrently, all the samples in a chromosomal ");
                msg += "region must contain the same number of markers, the region ";
                msg += ToString(unit.region) + " has samples with ";
                msg += ToString(markit->second) + " markers and ";
                msg += ToString(unit.markers) + " markers.\n\n";
                WrapAndPrintToCout(msg);
                return false;
            }
        }
        // check if each region has a consistent datatype, using a map
        typeit = typemap.find(unit.region);
        if (typeit == typemap.end())
            typemap.insert(make_pair(unit.region,unit.datatype));
        else
        {
            if (!ciStringEqual((*typeit).second, unit.datatype))
            {
                string msg("\nCurrently, Lamarc cannot handle diverse datatypes ");
                msg += "within a single chromosomal region.  The region ";
                msg += ToString(unit.region) + " has both ";
                msg += ToString(typeit->second) + " and " + ToString(unit.datatype);
                msg += " markers within it.\n\n";
                WrapAndPrintToCout(msg);
                return false;
            }
        }
    }
    return true;
} // RegionsValid

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

bool PopRegionRelation::PopsValid() const
{
    unsigned long i;

    for (i = 0; i < units.size(); ++i)
    {
        const Unit& unit = units[i];
        if (unit.population == "")
        {
            string msg("\nEmpty population name\n\n",CONVUI_ERRORINDENT);
            cout << MakeIndent(msg,CONVUI_ERRORINDENT) << endl;
            return false;
        }
    }
    return true;
} // PopsValid

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

void PopRegionRelation::WrapAndPrintToCout(const string& msg) const
{
    StringVec1d wrapped_msg(Linewrap(msg,CONVUI_LINELENGTH,
                                     CONVUI_ERRORINDENT));
    std::copy(wrapped_msg.begin(),wrapped_msg.end(),
              ostream_iterator<string>(cout, "\n"));

} // PopRegionRelation

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

bool PopRegionRelation::OverallValid() const
{
    if (!RegionsValid()) return false;
    if (!PopsValid()) return false;

    // check if every region has all populations

    // list all populations for each region
    unsigned long i;
    RegPopNameMap regionpops;
    for (i = 0; i < units.size(); ++i)
    {
        const Unit& unit = units[i];
        RegPopNameMap::iterator it = regionpops.find(unit.region);
        if (it == regionpops.end())
        {
            PopNameSet newpop;
            newpop.insert(unit.population);
            regionpops.insert(make_pair(unit.region, newpop));
            continue;
        }
        else
        {
            PopNameSet::iterator sit = (*it).second.find(unit.population);
            if (sit == (*it).second.end())
            {
                (*it).second.insert(unit.population);
            }
            else
            {
                string msg("\nThe chromosomal region ");
                msg += ToString(unit.region) + " has at least two populations named ";
                msg += ToString(unit.population) + ".\n\n";
                WrapAndPrintToCout(msg);
                return false;
            }
        }
    }

    // check that all lists are identical
    RegPopNameMap :: iterator firstregion = regionpops.begin();
    RegPopNameMap :: iterator iter = regionpops.begin();
    for (++iter; iter != regionpops.end(); ++iter)
    {
        if ((*iter).second != (*firstregion).second)
        {
            string msg("\nLamarc requires that all chromosomal regions ");
            msg += "contain samples from all of the same populations.  Region ";
            msg += ToString(iter->first) + " has samples from: ";
            PopNameSet::iterator pop(iter->second.begin());
            msg += *pop;
            for(++pop; pop != iter->second.end(); ++pop)
            {
                //JDEBUG--this doesn't work so...
                //if (std::find_if(pop->begin(),pop->end(),
                //                static_cast<int(*)(int)>(isalnum)))
                if (getFirstInterestingChar(*pop) != '\0')
                    msg += ", " + *pop;
            }
            msg += "; while region " + ToString(firstregion->first) + " has: ";
            pop = firstregion->second.begin();
            msg += *pop;
            for(++pop; pop != firstregion->second.end(); ++pop)
            {
                if (getFirstInterestingChar(*pop) != '\0')
                    msg += ", " + *pop;
            }
            msg += ".\n\n";
            WrapAndPrintToCout(msg);
            return false;
        }
    }

    return true;
} // OverallValid

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

RegByPopMap PopRegionRelation::GetRegionalByPopMap() const
{
    RegByPopMap htable;

    set<string> regionnames = GetRegionNames();
    set<string> popnames = GetPopulationNames();
    set<string>::iterator regname, popname;
    for(regname = regionnames.begin(); regname != regionnames.end();
        ++regname)
    {
        PopIterMap region;

        for(popname = popnames.begin(); popname != popnames.end(); ++popname)
        {
            PopIterVec datasets;
            vector<Unit>::const_iterator unit;
            for(unit = units.begin(); unit != units.end(); ++unit)
            {
                if (unit->IsIn(*regname,*popname))
                    datasets.push_back(unit->m_pop);
            }
            region.insert(make_pair(*popname,datasets));
        }

        htable.insert(make_pair(*regname,region));
    }

    return htable;

} // GetRegionalByPopMap

//____________________________________________________________________________________
