/*
 * Copyright (c) 2006-2007 The Regents of The University of Michigan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Gabe Black
 */

#include "tracechild.hh"
#include "printer.hh"

using namespace std;

//Types of printers. If none is found, or there is an error in the input,
//there are psuedo types to return.
enum PrinterType {PRINTER_NONE, PRINTER_ERROR, PRINTER_NESTING, PRINTER_REG};

int findEndOfRegPrinter(string, int);
int findEndOfNestingPrinter(string, int);
PrinterType findSub(string, int &, int &);

//This is pretty easy. Just find the closing parenthesis.
int findEndOfRegPrinter(string config, int startPos)
{
    int pos = config.find(")", startPos);
    if(pos == string::npos)
    {
        cerr << "Couldn't find the closing parenthesis for a reg printer" << endl;
        return 0;
    }
    return pos;
}

//This is a little harder. We need to make sure we don't
//grab an ending parenthesis that belongs to the nesting printer.
int findEndOfNestingPrinter(string config, int startPos)
{
    int length = config.length();
    int pos = startPos;
    int endPos = length;
    int parenPos = config.find(")", pos);
    //If we didn't find an ending parenthesis at all, we're in trouble
    if(parenPos == string::npos)
    {
        cerr << "Couldn't find the closing parenthesis for a nesting printer on the first try" << endl;
        return 0;
    }
    //Keep pulling out embedded stuff until we can't any more
    //we need to make sure we aren't skipping over the parenthesis
    //that ends -this- printer.
    PrinterType type = findSub(config, pos, endPos);
    if(type == PRINTER_ERROR)
        return 0;
    while(type != PRINTER_NONE && endPos >= parenPos)
    {
        //Find the next closest ending parenthesis since we passed
        //up the last one
        parenPos = config.find(")", endPos + 1);
        //If we didn't find one, we're in trouble
        if(parenPos == string::npos)
        {
            cerr << "Couldn't find the closing parenthesis for a nested printer on later tries" << endl;
            return 0;
        }
        //Start looking for the end of this printer and embedded
        //stuff past the one we just found
        pos = endPos + 1;
        //Reset endPos so we search to the end of config
        endPos = length;
        type = findSub(config, pos, endPos);
        if(type == PRINTER_ERROR)
            return 0;
    }
    //We ran out of embedded items, and we didn't pass up our last
    //closing paren. This must be the end of this printer.
    return parenPos;
}

//Find a sub printer. This looks for things which have a type defining
//character and then an opening parenthesis. The type is returned, and
//startPos and endPos are set to the beginning and end of the sub printer
//On entry, the search starts at index startPos and ends at either index
//endPos or a closing parenthesis, whichever comes first
PrinterType findSub(string config, int & startPos, int & endPos)
{
    int length = config.length();
    //Figure out where the different types of sub printers may start
    int regPos = config.find("%(", startPos);
    int nestingPos = config.find("~(", startPos);
    //If a type of printer wasn't found, say it was found too far away.
    //This simplifies things later
    if(regPos == string::npos)
        regPos = endPos;
    if(nestingPos == string::npos)
        nestingPos = endPos;
    //If we find a closing paren, that marks the
    //end of the region we're searching.
    int closingPos = config.find(")", startPos);
    if(closingPos != string::npos &&
            closingPos < regPos &&
            closingPos < nestingPos)
        return PRINTER_NONE;
    //If we didn't find anything close enough, say so.
    if(regPos >= endPos && nestingPos >= endPos)
        return PRINTER_NONE;
    //At this point, we know that one of the options starts legally
    //We need to find which one is first and return that
    if(regPos < nestingPos)
    {
        int regEnd = findEndOfRegPrinter(config, regPos + 2);
        //If we couldn't find the end...
        if(!regEnd)
        {
            cerr << "Couldn't find the end of the reg printer" << endl;
            return PRINTER_ERROR;
        }
        //Report the sub printer's vitals.
        startPos = regPos;
        endPos = regEnd;
        return PRINTER_REG;
    }
    else
    {
        int nestingEnd = findEndOfNestingPrinter(config, nestingPos + 2);
        //If we couldn't find the end...
        if(!nestingEnd)
        {
            cerr << "Couldn't find the end of the nesting printer" << endl;
            return PRINTER_ERROR;
        }
        //Report the sub printer's vitals.
        startPos = nestingPos;
        endPos = nestingEnd;
        return PRINTER_NESTING;
    }
    return PRINTER_NONE;
}

//Set up a nesting printer. This printer can contain sub printers
bool NestingPrinter::configure(string config)
{
    //Clear out any old stuff
    constStrings.clear();
    numPrinters = 0;
    printers.clear();
    int length = config.length();
    int startPos = 0, endPos = length;
    int lastEndPos = -1;
    //Try to find a sub printer
    PrinterType type = findSub(config, startPos, endPos);
    if(type == PRINTER_ERROR)
    {
        cerr << "Problem finding first sub printer" << endl;
        return false;
    }
    while(type != PRINTER_NONE)
    {
        string prefix = config.substr(lastEndPos + 1, startPos - lastEndPos - 1);
        lastEndPos = endPos;
        constStrings.push_back(prefix);
        string subConfig, subString;
        long int commaPos, lastCommaPos, childSwitchVar;
        //Set up the register printer
        RegPrinter * regPrinter = new RegPrinter(child);
        NestingPrinter * nestingPrinter = new NestingPrinter(child);
        switch(type)
        {
            //If we found a plain register printer
          case PRINTER_REG:
            numPrinters++;
            //Get the register name
            subConfig = config.substr(startPos + 2, endPos - startPos - 2);
            if(!regPrinter->configure(subConfig))
            {
                delete regPrinter;
                cerr << "Error configuring reg printer" << endl;
                return false;
            }
            printers.push_back(regPrinter);
            break;
            //If we found an embedded nesting printer
          case PRINTER_NESTING:
            numPrinters++;
            //Punt on reading in all the parameters of the nesting printer
            subConfig = config.substr(startPos + 2, endPos - startPos - 2);
            lastCommaPos = string::npos;
            commaPos = subConfig.find(",");
            if(commaPos == string::npos)
                return false;
            childSwitchVar = child->getRegNum(subConfig.substr(0, commaPos));
            if(childSwitchVar == -1)
            {
                cerr << "Couldn't configure switching variable!" << endl;
                return false;
            }
            //Eat up remaining arguments
            while(commaPos != string::npos)
            {
                lastCommaPos = commaPos;
                commaPos = subConfig.find(",", commaPos + 1);
            }
            if(lastCommaPos != string::npos)
            {
                subConfig = subConfig.substr(lastCommaPos + 1, subConfig.length() - lastCommaPos - 1);
            }
            if(!nestingPrinter->configure(subConfig))
            {
                delete nestingPrinter;
                cerr << "Error configuring nesting printer" << endl;
                return false;
            }
            nestingPrinter->switchVar = childSwitchVar;
            printers.push_back(nestingPrinter);
            break;
          default:
            cerr << "Unrecognized printer type" << endl;
            return false;
        }
        //Move down past what we just parsed
        startPos = endPos + 1;
        endPos = length;
        type = findSub(config, startPos, endPos);
        if(type == PRINTER_ERROR)
        {
            cerr << "Unable to find subprinters on later tries" << endl;
            return false;
        }
    }
    //Put in the trailing stuff
    string trailer = config.substr(startPos, length - startPos);
    constStrings.push_back(trailer);
    return true;
}

bool RegPrinter::configure(string config)
{
    //Figure out what our register number is based on the name we're given
    int num = child->getRegNum(config);
    if(num == -1)
    {
        cerr << "Couldn't find register " << config << endl;
        return false;
    }
    regNum(num);
    return true;
}

ostream & NestingPrinter::writeOut(ostream & os)
{
    if(switchVar == -1 || child->diffSinceUpdate(switchVar))
    {
        int x;
        for(x = 0; x < numPrinters; x++)
        {
            os << constStrings[x];
            os << printers[x];
        }
        os << constStrings[x];
    }
    return os;
}

ostream & RegPrinter::writeOut(ostream & os)
{
    os << child->printReg(intRegNum);
    return os;
}