#include <fstream>
#include <cstdio>
#include <cassert>

#include "myDbEnv.hpp"
#include "myXmlContainer.hpp"

using namespace DbXml;

void usage()
{
     std::cerr  <<  "This program updates a document found in a DBXML container. You should\n"
                <<  "pre-load the container using loadExamplesData.[sh|cmd] before running this\n"
                <<  "example. You are only required to pass this command the path location of the\n"
                <<  "database environment that you specified when you pre-loaded the examples\n"
                <<  "data:\n\n"

                <<   "\t-h <dbenv directory>" << std::endl;
        exit( -1 );
}



//forward declaration
std::string getValue( const XmlDocument &document, const std::string &XPath, XmlQueryContext &context );

void getNewDocument( XmlDocument &theDocument, XmlQueryContext &context, std::string &docString);

void doUpdateDocument( XmlContainer &container, const std::string &XPath, XmlQueryContext &context, DbTxn *txnid )
{
    ////////////////////////////////////////////////////////////////////////
    //////  Updates a document stored in a DBXML container.          ///////
    ////////////////////////////////////////////////////////////////////////

    ///// some defensive code eliminated for clarity //

    try {
        std::cout << "Updating document for expression: '" << XPath << "' " << std::endl;
        std::cout << "Return to continue: ";
        getc(stdin);

        XmlResults results( container.queryWithXPath(txnid, XPath, &context ) );
        std::cout << "Found " << results.size() << " documents matching the expression '" << XPath << ". " << std::endl;
        //XmlValue value;
        std::cout << "\n";
        XmlDocument theDocument;
        while( results.next(theDocument) )
        {
            std::string docString;
            //Retrieve the document's value as a string.
            theDocument.getContentAsString(docString);
            
            std::cout << "Updating document: \n" << docString << std::endl;
            std::cout << "Return to continue: ";
            getc(stdin);

            //Update the document. We do it as a string.

            //This next function just modifies the document string
            //in a small way.
            getNewDocument( theDocument, context, docString);

            std::cout << "Updating document..." <<  std::endl;

            //Set the document's content to be the new document string
            theDocument.setContent( docString );

            //Now replace the document in the container
            container.updateDocument(txnid, theDocument);
            std::cout << "Document updated." <<  std::endl;
        }
    }
    //Catches XmlException
    catch(std::exception &e)
    {
        std::cerr << "Document deletion failed. ";
        std::cerr << e.what() << "\n";

        //Abort the transaction. All changes made to the database since the start
        //of this transaction are discarded.
        txnid->abort();

        exit( -1 );
    }

}

int main(int argc, char **argv)
{

    std::string path2DbEnv;
    std::string theContainer = "namespaceExampleData.dbxml";
    for ( int i=1; i<argc; i++ )
    {
        if ( argv[i][0] == '-' )
        {
            switch(argv[i][1])
            {
              case 'h':
                path2DbEnv = argv[++i];
                break;
              default:
                usage();
            }
         }
     }

     if (! path2DbEnv.length() )
        usage();

     //open a container in the db environment
     myDbEnv theDBEnv( path2DbEnv ); 
     myXmlContainer openedContainer( theContainer, theDBEnv );


     //myDbEnv and myXmlContainer open with transactions. All subsequent
     //writes to them must also be performed inside a transaction.

     //Get a transaction
     DbTxn *txn;
     theDBEnv.getDbEnv().txn_begin(0, &txn, 0);

    //create a context and declare the namespaces
    XmlQueryContext context;
    context.setNamespace( "fruits", "http://groceryItem.dbxml/fruits");
    context.setNamespace( "vegetables", "http://groceryItem.dbxml/vegetables");
    context.setNamespace( "desserts", "http://groceryItem.dbxml/desserts");

    //update the document that describes Zapote Blanco (a fruit)
    doUpdateDocument( openedContainer.getContainer(), "/fruits:item/product[text() = 'Zapote Blanco']", 
                      context, txn);

    txn->commit( 0 );

    return 0;
}

std::string getValue( const XmlDocument &document, const std::string &XPath, XmlQueryContext &context )
{
    /////////////////////////////////////////////////////////////////////////////////
    ///////////    Return specific information from a document. ///////////////////// 
    ///////////   !!!!!! Assumes a result set of size 1 !!!!!!! /////////////////////
    /////////////////////////////////////////////////////////////////////////////////
    

    // Exception handling omitted....
    
    //We don't want a document, we want a specific value. So set the
    //return type to Result Values
    context.setReturnType( XmlQueryContext::ResultValues );

    //Perform the query
    XmlResults result = document.queryWithXPath(XPath, &context);

    //We require a result set size of 1.
    assert( result.size() == 1 );

    //Get the value. If we allowed the result set to be larger than size 1,
    //we would have to loop through the results, processing each as is
    //required by our application.
    XmlValue value;
    result.next(value);

    //Set the result type back to Result Document
    context.setReturnType( XmlQueryContext::ResultDocuments );
    return value.asString( 0 );
        
}

//Simple little function to replace part of a string with a new value.
//All this does is add an 'A' to the end of the document's inventory
//value. So each time you run this program, the inventory value should
//get longer by one 'A'.
void getNewDocument( XmlDocument &theDocument, XmlQueryContext &context, std::string &docString)
{
        //get the substring that we want to replace
        std::string inventory = getValue( theDocument, "/*/inventory/inventory/text()", context );
        int inventoryLength = inventory.length();
            
        //find the substring in the original document string
        int pos = docString.find(inventory);
        if (pos == std::string::npos)
        {
            std::cerr << "Error: inventory string: '" << inventory << "' not found in document string:" << std::endl;
            std::cerr << docString << std::endl;
        }
        int insertPos = pos + inventoryLength;
        docString.insert(insertPos,"A");
}
