// $Id: display.cpp,v 1.42 2018/01/03 21:33:01 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: Peter Beerli, Mary Kuhner, Jon Yamato and Joseph Felsenstein */


// base class for all displays
// + derived classes (these may need to split away from this file
//
// Peter Beerli (March 22 2001)

#include <string>
#include <iostream>
#include "constraintmenus.h"
#include "dialognoinput.h"
#include "display.h"
#include "errhandling.h"
#include "menu_strings.h"
#include "menuitem.h"
#include "newmenu.h"
#include "stringx.h"
#include "twodtable.h"

using namespace std;

const size_t SCREENWIDTH = 75;
const long   MENUPARAMETERWIDTH=24;
const long   MENUTEXTWIDTH=47;
const long   MENUKEYWIDTH=3;

// Scrolling display ----------------------------------
ScrollingDisplay::ScrollingDisplay() {};
ScrollingDisplay::~ScrollingDisplay() {};

void ScrollingDisplay::Title(const string & title, const string & info)
{
    string line(SCREENWIDTH,'-');

    StringVec1d titleUnits = MakeDisplayableUnits(title,SCREENWIDTH);
    StringVec1d infoUnits = MakeDisplayableUnits(info,SCREENWIDTH-5);

    cout << endl << line << endl;
    for(StringVec1d::iterator i=titleUnits.begin(); i != titleUnits.end(); i++)
    {
        cout << *i << endl;
    }
    for(StringVec1d::iterator j=infoUnits.begin(); j != infoUnits.end(); j++)
    {
        cout << "    " << *j  << endl;
    }
    //cout << endl;
}

void ScrollingDisplay::Warn(StringVec1d warnings)
{
    for(unsigned long warnNum= 0; warnNum < warnings.size(); warnNum++)
    {
        cout << endl;
        StringVec1d onewarning;
        onewarning.push_back(warnings[warnNum]);
        onewarning = Linewrap(onewarning, SCREENWIDTH);
        for(StringVec1d::iterator i=onewarning.begin(); i != onewarning.end(); i++)
        {
            cout << *i << endl;
        }
    }
}

menu_return_type ScrollingDisplay::DisplayNewMenu(NewMenu & menu)
{
    menu_return_type mrt = menu_REDISPLAY;
    while(mrt == menu_REDISPLAY)
    {
        Title(menu.Title(),menu.Info());
        ShowMenuDisplayQuantaVec(menu.MenuItems());
        Warn(menu.Warnings());
        string input = GetUserInput();
        if(menu.Handles(input))
        {
            MenuInteraction_ptr mi = menu.GetHandler(input);
            try
            {
                mrt = mi->InvokeMe(*this);
                // delete mi;  handled by smart pointer
            }
            catch (const data_error& e)
            {
                menu.AddWarning(e.what());
                return menu_REDISPLAY;
            }
        }
        // verify that this part of the menu is consistent
        // before fully exiting
        if((mrt == menu_RUN) || (mrt == menu_GO_UP))
        {
            //
            StringVec1d inconsistents = menu.GetInconsistencies();
            if(!inconsistents.empty())
            {
                ChideOnInconsistencies(inconsistents);
                mrt = menu_REDISPLAY;
            }
        }
    }
    switch(mrt)
    {
        case menu_RUN:
            return menu_RUN;
            break;
        case menu_QUIT:
            return menu_QUIT;
            break;
        default:
            return menu_REDISPLAY;
    }

    return menu_REDISPLAY;
}

void ScrollingDisplay::ChideOnInconsistencies(StringVec1d inconsistents)
{
    DialogChideInconsistencies dci(inconsistents);
    dci.InvokeMe(*this);
}

menu_return_type ScrollingDisplay::DisplayRaw(StringVec1d strings)
{
    StringVec1d::iterator i;
    for(i=strings.begin();i!= strings.end();i++)
    {
        string s = *i;
        DisplayDialogOutput(s);
    }
    return menu_REDISPLAY;
}

void ScrollingDisplay::ShowMenuDisplayQuantaVec(MenuDisplayQuantaVec lines)
{
    MenuDisplayQuantaVec :: iterator menuline;
    for(menuline=lines.begin(); menuline != lines.end(); menuline++)
    {
        MenuDisplayQuanta * item = *menuline;
        item->DisplayItemOn(*this);//Probably a call-back to ShowMenuDisplayLine
    }
}

void ScrollingDisplay::ShowMenuDisplayGroup(MenuDisplayGroup & group)
{
    vector<UIId> visibleIds = group.GetVisibleIds();
    vector<UIId>::iterator ids;
    DisplayOneLine(group.GetGroupDescription());
    if (visibleIds.begin() == visibleIds.end())
    {
        DisplayOneLine(group.GetEmptyDescription());
    }
    for(ids=visibleIds.begin(); ids != visibleIds.end(); ids++)
    {
        UIId id = *ids;
        string menuKey = group.GetKey(id);
        string storedMenuText = group.GetText(id);
        string generatedMenuText = group.GetVariableText(id);
        ShowOneMenuLine(menuKey,storedMenuText,generatedMenuText);
        if (group.HasMultiLineItems())
        {
            ShowExtraMenuLines(group.GetExtraText(id),group.GetExtraVariableText(id));
        }
    }
}

void ScrollingDisplay::ShowMenuDisplay2dGroup(ToggleMenuItem2dGroup & groups)
{
    UIIdVec2d visibleIds = groups.GetVisibleIds();
    long listnum = 0;
    DisplayOneLine(groups.GetGroupDescription());
    if (visibleIds.begin() == visibleIds.end())
    {
        DisplayOneLine(groups.GetEmptyDescription());
    }
    for(UIIdVec2d::iterator idlists=visibleIds.begin();
        idlists != visibleIds.end(); idlists++, listnum++)
    {
        UIIdVec1d idlist = *idlists;
        DisplayOneLine(groups.GetGroupDescription(listnum));
        if (idlist.begin() == idlist.end())
        {
            DisplayOneLine(groups.GetEmptyDescription(listnum));
        }

        for (UIIdVec1d::iterator ids = idlist.begin();
             ids != idlist.end(); ids++)
        {
            UIId id = *ids;
            string menuKey = groups.GetKey(id);
            string storedMenuText = groups.GetText(id);
            string generatedMenuText = groups.GetVariableText(id);
            ShowOneMenuLine(menuKey,storedMenuText,generatedMenuText);
            if (groups.HasMultiLineItems())
            {
                ShowExtraMenuLines(groups.GetExtraText(id),groups.GetExtraVariableText(id));
            }
        }
    }
}

void ScrollingDisplay::ShowMenuDisplayLine(MenuDisplayLine & line)
{
    if(line.IsVisible())
    {
        string menuKey = line.GetKey();
        string storedMenuText = line.GetText();
        string generatedMenuText = line.GetVariableText();
        ShowOneMenuLine(menuKey,storedMenuText,generatedMenuText);
        if (line.HasMultiLineItems())
        {
            ShowExtraMenuLines(line.GetExtraText(),line.GetExtraVariableText());
        }
    }
}

vector<string> ScrollingDisplay::BreakAtCarriageReturns(string toBreak)
{
    vector<string> strings;
    string::size_type prevStringPos;
    string::size_type stringPos;
    string stringFragment;

    prevStringPos = 0;
    // loop through the toBreak string, finding each "\n" character and
    // stuffing each substring into the return vector
    while( (stringPos = toBreak.find("\n",prevStringPos)) != string::npos)
    {
        stringFragment.assign(toBreak,prevStringPos,stringPos-prevStringPos);
        prevStringPos = stringPos+1;
        strings.push_back(stringFragment);
    }
    // remaining string has no carriage return
    stringFragment.assign(toBreak,prevStringPos,string::npos);
    if(!stringFragment.empty())
    {
        strings.push_back(stringFragment);
    }
    return strings;
}

// break "toShow" string into substrings no longer than "width"
// units wide
vector<string> ScrollingDisplay::MakeDisplayableUnits(string toShow,long width)
{
    string::size_type tabpos = toShow.find("\t");
    while (tabpos != string::npos)
    {
        toShow.replace(tabpos,1,"    ");
        tabpos = toShow.find("\t");
    }
    vector<string> returnStrings;
    vector<string> withoutCarriageReturns = BreakAtCarriageReturns(toShow);
    vector<string>::iterator nextStringIter;
    for(nextStringIter = withoutCarriageReturns.begin();
        nextStringIter != withoutCarriageReturns.end();
        nextStringIter++)
    {
        long currentwidth = width;
        string remaining = *nextStringIter;
        //Find any indenting
        unsigned long indentlength = 0;
        string::size_type indent = remaining.find(" ");
        while (indent == 0)
        {
            currentwidth--;
            indentlength++;
            remaining.erase(0,1);
            indent = remaining.find(" ");
        }
        while((long)(remaining.length()) > currentwidth)
        {
            unsigned long breakHere
                = remaining.find_last_of(menustr::space,currentwidth);
            string thisLine = remaining.substr(0,breakHere);
            thisLine = string(indentlength, ' ') + thisLine;
            returnStrings.push_back(thisLine);
            remaining.erase(0,breakHere);
        }
        remaining = string(indentlength, ' ') + remaining;
        returnStrings.push_back(remaining);
    }

    return returnStrings;
}

void ScrollingDisplay::ShowOneMenuLine(
    string menuKey, string storedMenuText, string generatedMenuText)
{
    vector<string> middleParts = MakeDisplayableUnits(storedMenuText,MENUTEXTWIDTH);
    vector<string> rightParts = MakeDisplayableUnits(generatedMenuText,MENUPARAMETERWIDTH);
    vector<string>::iterator middleStrings;
    vector<string>::iterator rightStrings;
    bool firstTime = true;
    for(middleStrings = middleParts.begin(), rightStrings = rightParts.begin();
        middleStrings != middleParts.end();
        middleStrings++)
    {
        string key;
        if(firstTime)
        {
            key = MakeJustified(menuKey, MENUKEYWIDTH);
            firstTime = false;
        }
        else
        {
            key = MakeJustified(menustr::emptyString, MENUKEYWIDTH);
        }

        if(rightStrings != rightParts.end())
        {
            DisplayDialogOutput(key
                                + "  "
                                + MakeJustified(*middleStrings, -MENUTEXTWIDTH)
                                + MakeJustified(*rightStrings, MENUPARAMETERWIDTH));
            rightStrings++;
        }
        else
        {
            DisplayDialogOutput(key
                                + "  "
                                + MakeJustified(*middleStrings, -MENUTEXTWIDTH)
                                + MakeJustified(menustr::emptyString, MENUPARAMETERWIDTH));
        }

    }
    for( ; rightStrings != rightParts.end(); rightStrings++)
    {
        DisplayDialogOutput(MakeJustified(menustr::emptyString, MENUKEYWIDTH)
                            + "  "
                            + MakeJustified(menustr::emptyString, -MENUTEXTWIDTH)
                            + MakeJustified(*rightStrings, MENUPARAMETERWIDTH));
    }
}

void ScrollingDisplay::ShowExtraMenuLines( std::vector<string> storedMenuText, std::vector<string> generatedMenuText)
{
    assert(storedMenuText.size() == generatedMenuText.size());
    std::vector<string>::iterator si;
    std::vector<string>::iterator gi;
    for(si=storedMenuText.begin(),gi=generatedMenuText.begin();
        si != storedMenuText.end() && gi != generatedMenuText.end();
        si++,gi++)
    {
        string s_string = *si;
        string g_string = *gi;
        // JIMFIX -- if you really want an indent, you'll need to refactor ShowOneMenuLine
        // so that this call to it makes the s_string argument indent
        ShowOneMenuLine(menustr::emptyString,s_string,g_string);
    }
}

void ScrollingDisplay::ShowMenuDisplayTable(MenuDisplayTable & t)
{
    if(t.IsVisible())
    {
        cout << t.CreateDisplayString();
    }
}

void ScrollingDisplay::Prompt()
{
    cout << endl << ">>> ";
    cout.flush();
}

string ScrollingDisplay::GetUserInput()
{
    Prompt();
    string sline;
    MyCinGetline(sline);
    return sline;
}

void ScrollingDisplay::DisplayOneLine(string output)
{
    if((output == "") || (output == "\n"))
    {
        return;
    }
    cout << output << endl;
}

// displays the string "output" one line at a time
// intended to later be extended to allow adjustment of
// display width and indenting if desired
void ScrollingDisplay::DisplayDialogOutput(string output)
{
    string::size_type prevStringPos;
    string::size_type stringPos;
    string stringFragment;

    prevStringPos = 0;
    // loop through the output string, finding each "\n" character and
    // printing one line at a time
    while( (stringPos = output.find("\n",prevStringPos)) != string::npos)
    {
        stringFragment.assign(output,prevStringPos,stringPos-prevStringPos);
        prevStringPos = stringPos+1;
        DisplayOneLine(stringFragment);
    }
    // remaining string doesn't have a "\n", so print it anyway
    stringFragment.assign(output,prevStringPos,string::npos);
    if(!stringFragment.empty())
    {
        DisplayOneLine(stringFragment);
    }
}

// No display ----------------------------------
NoDisplay::NoDisplay() {};
NoDisplay::~NoDisplay() {};

menu_return_type NoDisplay::DisplayNewMenu(NewMenu &)
{
    return menu_REDISPLAY;
}

menu_return_type NoDisplay::DisplayRaw(StringVec1d)
{
    return menu_REDISPLAY;
}

void NoDisplay::ShowMenuDisplayGroup(MenuDisplayGroup &)
{
}

void NoDisplay::ShowMenuDisplay2dGroup(ToggleMenuItem2dGroup &)
{
}

void NoDisplay::ShowMenuDisplayLine(MenuDisplayLine &)
{
}

void NoDisplay::ShowMenuDisplayTable(MenuDisplayTable &)
{
}

string NoDisplay::GetUserInput()
{
    throw impossible_error("Should never get input without display");
}

void NoDisplay::DisplayDialogOutput(string)
{
}

//____________________________________________________________________________________
