/***************************************************************************
                          webpricequote.cpp
                             -------------------
    begin                : Thu Dec 30 2004
    copyright            : (C) 2004 by Ace Jones
    email                : Ace Jones <acejones@users.sourceforge.net>
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

// ----------------------------------------------------------------------------
// QT Headers

#include <tqfile.h>
#include <tqregexp.h>
#include <tqtextstream.h>
#include <tqprocess.h>

// ----------------------------------------------------------------------------
// KDE Headers

#include <tdeio/netaccess.h>
#include <tdeio/scheduler.h>
#include <kurl.h>
#include <tdelocale.h>
#include <kdebug.h>
#include <tdeapplication.h>
#include <tdeconfig.h>
#include <tdeglobal.h>
#include <kstandarddirs.h>
#include <kcalendarsystem.h>
#include <tdetempfile.h>

// ----------------------------------------------------------------------------
// Project Headers

#include "../mymoney/mymoneyexception.h"
#include "mymoneyqifprofile.h"
#include "webpricequote.h"

// define static members
TQString WebPriceQuote::m_financeQuoteScriptPath;
TQStringList WebPriceQuote::m_financeQuoteSources;

TQString * WebPriceQuote::lastErrorMsg;
int WebPriceQuote::lastErrorCode = 0;

WebPriceQuote::WebPriceQuote( TQObject* _parent, const char* _name ):
  TQObject( _parent, _name )
{
  m_financeQuoteScriptPath =
      TDEGlobal::dirs()->findResource("appdata", TQString("misc/financequote.pl"));
  connect(&m_filter,TQT_SIGNAL(processExited(const TQString&)),this,TQT_SLOT(slotParseQuote(const TQString&)));
}

WebPriceQuote::~WebPriceQuote()
{
}

bool WebPriceQuote::launch( const TQString& _symbol, const TQString& _id, const TQString& _sourcename )
{
  if (_sourcename.contains("Finance::Quote"))
    return (launchFinanceQuote (_symbol, _id, _sourcename));
  else
    return (launchNative (_symbol, _id, _sourcename));
}

bool WebPriceQuote::launchNative( const TQString& _symbol, const TQString& _id, const TQString& _sourcename ) {
  bool result = true;
  m_symbol = _symbol;
  m_id = _id;

//   emit status(TQString("(Debug) symbol=%1 id=%2...").arg(_symbol,_id));

  // if we're running normally, with a UI, we can just get these the normal way,
  // from the config file
  if ( kapp )
  {
    TQString sourcename = _sourcename;
    if ( sourcename.isEmpty() )
      sourcename = "Yahoo";

    if ( quoteSources().contains(sourcename) )
      m_source = WebPriceQuoteSource(sourcename);
    else
      emit error(TQString("Source <%1> does not exist.").arg(sourcename));
  }
  // otherwise, if we have no kapp, we have no config.  so we just get them from
  // the defaults
  else
  {
    if ( _sourcename.isEmpty() )
      m_source = defaultQuoteSources()["Yahoo"];
    else
      m_source = defaultQuoteSources()[_sourcename];
  }

  KURL url;

  // if the source has room for TWO symbols..
  if ( m_source.m_url.contains("%2") )
  {
    // this is a two-symbol quote.  split the symbol into two.  valid symbol
    // characters are: 0-9, A-Z and the dot.  anything else is a separator
    TQRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)",false /*case sensitive*/);

    // if we've truly found 2 symbols delimited this way...
    if ( splitrx.search(m_symbol) != -1 )
      url = KURL::fromPathOrURL(m_source.m_url.arg(splitrx.cap(1),splitrx.cap(2)));
    else
      kdDebug(2) << "WebPriceQuote::launch() did not find 2 symbols" << endl;
  }
  else
    // a regular one-symbol quote
    url = KURL::fromPathOrURL(m_source.m_url.arg(m_symbol));

  // If we're running a non-interactive session (with no UI), we can't
  // use TDEIO::NetAccess, so we have to get our web data the old-fashioned
  // way... with 'wget'.
  //
  // Note that a 'non-interactive' session right now means only the test
  // cases.  Although in the future if KMM gains a non-UI mode, this would
  // still be useful
  if ( ! kapp && ! url.isLocalFile() )
    url = KURL::fromPathOrURL("/usr/bin/wget -O - " + url.prettyURL());

  if ( url.isLocalFile() )
  {
    emit status(TQString("Executing %1...").arg(url.path()));

    m_filter.clearArguments();
    m_filter << TQStringList::split(" ",url.path());
    m_filter.setSymbol(m_symbol);

    // if we're running non-interactive, we'll need to block.
    // otherwise, just let us know when it's done.
    TDEProcess::RunMode mode = TDEProcess::NotifyOnExit;
    if ( ! kapp )
      mode = TDEProcess::Block;

    if(m_filter.start(mode, TDEProcess::All))
    {
      result = true;
      m_filter.resume();
    }
    else
    {
      emit error(TQString("Unable to launch: %1").arg(url.path()));
      slotParseQuote(TQString());
    }
  }
  else
  {
    emit status(TQString("Fetching URL %1...").arg(url.prettyURL()));

    TQString tmpFile;
    if( download( url, tmpFile, NULL ) )
    {
      kdDebug(2) << "Downloaded " << tmpFile << endl;
      TQFile f(tmpFile);
      if ( f.open( IO_ReadOnly ) )
      {
        result = true;
        TQString quote = TQTextStream(&f).read();
        f.close();
        slotParseQuote(quote);
      }
      else
      {
        slotParseQuote(TQString());
      }
      removeTempFile( tmpFile );
    }
    else
    {
      emit error(TDEIO::NetAccess::lastErrorString());
      slotParseQuote(TQString());
    }
  }
  return result;
}

void WebPriceQuote::removeTempFile(const TQString& tmpFile)
{
  if(tmpFile == m_tmpFile) {
    unlink(tmpFile);
    m_tmpFile = TQString();
  }
}

bool WebPriceQuote::download(const KURL& u, TQString & target, TQWidget* window)
{
  m_tmpFile = TQString();

  // the following code taken and adapted from TDEIO::NetAccess::download()
  if (target.isEmpty())
  {
    KTempFile tmpFile;
    target = tmpFile.name();
    m_tmpFile = target;
  }

  KURL dest;
  dest.setPath( target );


  // the following code taken and adapted from TDEIO::NetAccess::filecopyInternal()
  bJobOK = true; // success unless further error occurs

  TDEIO::Scheduler::checkSlaveOnHold(true);
  TDEIO::Job * job = TDEIO::file_copy( u, dest, -1, true, false, false );
  job->setWindow (window);
  job->addMetaData("cache", "reload");  // bypass cache
  connect( job, TQT_SIGNAL( result (TDEIO::Job *) ),
           this, TQT_SLOT( slotResult (TDEIO::Job *) ) );

  enter_loop();
  return bJobOK;

}

// The following parts are copied and adjusted from TDEIO::NetAccess

// If a troll sees this, he kills me
void tqt_enter_modal( TQWidget *widget );
void tqt_leave_modal( TQWidget *widget );

void WebPriceQuote::enter_loop(void)
{
  TQWidget dummy(0,0,WType_Dialog | WShowModal);
  dummy.setFocusPolicy( TQ_NoFocus );
  tqt_enter_modal(&dummy);
  tqApp->enter_loop();
  tqt_leave_modal(&dummy);
}

void WebPriceQuote::slotResult( TDEIO::Job * job )
{
  lastErrorCode = job->error();
  bJobOK = !job->error();
  if ( !bJobOK )
  {
    if ( !lastErrorMsg )
      lastErrorMsg = new TQString;
    *lastErrorMsg = job->errorString();
  }

  tqApp->exit_loop();
}
// The above parts are copied and adjusted from TDEIO::NetAccess

bool WebPriceQuote::launchFinanceQuote ( const TQString& _symbol, const TQString& _id,
                               const TQString& _sourcename ) {
  bool result = true;
  m_symbol = _symbol;
  m_id = _id;
  TQString FTQSource = _sourcename.section (" ", 1);
  m_source = WebPriceQuoteSource (_sourcename, m_financeQuoteScriptPath,
                                  "\"([^,\"]*)\",.*",  // symbol regexp
                                  "[^,]*,[^,]*,\"([^\"]*)\"", // price regexp
                                  "[^,]*,([^,]*),.*", // date regexp
                                  "%y-%m-%d"); // date format

  //emit status(TQString("(Debug) symbol=%1 id=%2...").arg(_symbol,_id));


  m_filter.clearArguments();
  m_filter << "perl" << m_financeQuoteScriptPath << FTQSource << TDEProcess::quote(_symbol);
  m_filter.setUseShell(true);
  m_filter.setSymbol(m_symbol);
  emit status(TQString("Executing %1 %2 %3...").arg(m_financeQuoteScriptPath).arg(FTQSource).arg(_symbol));

    // if we're running non-interactive, we'll need to block.
    // otherwise, just let us know when it's done.
  TDEProcess::RunMode mode = TDEProcess::NotifyOnExit;
  if ( ! kapp )
    mode = TDEProcess::Block;

  if(m_filter.start(mode, TDEProcess::All))
  {
    result = true;
    m_filter.resume();
  }
  else
  {
    emit error(TQString("Unable to launch: %1").arg(m_financeQuoteScriptPath));
    slotParseQuote(TQString());
  }

  return result;
}

void WebPriceQuote::slotParseQuote(const TQString& _quotedata)
{
  TQString quotedata = _quotedata;
  bool gotprice = false;
  bool gotdate = false;

//    kdDebug(2) << "WebPriceQuote::slotParseQuote( " << _quotedata << " ) " << endl;

  if ( ! quotedata.isEmpty() )
  {
    if(!m_source.m_skipStripping) {
      //
      // First, remove extranous non-data elements
      //

      // HTML tags
      quotedata.remove(TQRegExp("<[^>]*>"));

      // &...;'s
      quotedata.replace(TQRegExp("&\\w+;")," ");

      // Extra white space
      quotedata = quotedata.simplifyWhiteSpace();
    }

#if KMM_DEBUG
    // Enable to get a look at the data coming back from the source after it's stripped
    TQFile file("stripped.txt");
    if ( file.open( IO_WriteOnly ) )
    {
      TQTextStream( &file ) << quotedata;
      file.close();
    }
#endif

    TQRegExp symbolRegExp(m_source.m_sym);
    TQRegExp dateRegExp(m_source.m_date);
    TQRegExp priceRegExp(m_source.m_price);

    if( symbolRegExp.search(quotedata) > -1)
      emit status(i18n("Symbol found: %1").arg(symbolRegExp.cap(1)));

    if(priceRegExp.search(quotedata)> -1)
    {
      gotprice = true;

      // Deal with european quotes that come back as X.XXX,XX or XX,XXX
      //
      // We will make the assumption that ALL prices have a decimal separator.
      // So "1,000" always means 1.0, not 1000.0.
      //
      // Remove all non-digits from the price string except the last one, and
      // set the last one to a period.
      TQString pricestr = priceRegExp.cap(1);

      int pos = pricestr.findRev(TQRegExp("\\D"));
      if ( pos > 0 )
      {
        pricestr[pos] = '.';
        pos = pricestr.findRev(TQRegExp("\\D"),pos-1);
      }
      while ( pos > 0 )
      {
        pricestr.remove(pos,1);
        pos = pricestr.findRev(TQRegExp("\\D"),pos);
      }

      m_price = pricestr.toDouble();
      emit status(i18n("Price found: %1 (%2)").arg(pricestr).arg(m_price));
    }

    if(dateRegExp.search(quotedata) > -1)
    {
      TQString datestr = dateRegExp.cap(1);

      MyMoneyDateFormat dateparse(m_source.m_dateformat);
      try
      {
        m_date = dateparse.convertString( datestr,false /*strict*/ );
        gotdate = true;
        emit status(i18n("Date found: %1").arg(m_date.toString()));;
      }
      catch (MyMoneyException* e)
      {
        // emit error(i18n("Unable to parse date %1 using format %2: %3").arg(datestr,dateparse.format(),e->what()));
        m_date = TQDate::currentDate();
        gotdate = true;
        delete e;
      }
    }

    if ( gotprice && gotdate )
    {
      emit quote( m_id, m_symbol, m_date, m_price );
    }
    else
    {
      emit error(i18n("Unable to update price for %1").arg(m_symbol));
      emit failed( m_id, m_symbol );
    }
  }
  else
  {
    emit error(i18n("Unable to update price for %1").arg(m_symbol));
    emit failed( m_id, m_symbol );
  }
}

TQMap<TQString,WebPriceQuoteSource> WebPriceQuote::defaultQuoteSources(void)
{
  TQMap<TQString,WebPriceQuoteSource> result;

  result["Yahoo"] = WebPriceQuoteSource("Yahoo",
    "http://finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d1",
    "\"([^,\"]*)\",.*",  // symbolregexp
    "[^,]*,([^,]*),.*", // priceregexp
    "[^,]*,[^,]*,\"([^\"]*)\"", // dateregexp
    "%m %d %y" // dateformat
  );

  result["Yahoo Currency"] = WebPriceQuoteSource("Yahoo Currency",
    "http://finance.yahoo.com/d/quotes.csv?s=%1%2=X&f=sl1d1",
    "\"([^,\"]*)\",.*",  // symbolregexp
    "[^,]*,([^,]*),.*", // priceregexp
    "[^,]*,[^,]*,\"([^\"]*)\"", // dateregexp
    "%m %d %y" // dateformat
  );

  // 2009-08-20 Yahoo UK has no quotes and has comma separators
  // sl1d1 format for Yahoo UK doesn't seem to give a date ever
  // sl1d3 gives US locale time (9:99pm) and date (mm/dd/yyyy)
  result["Yahoo UK"] = WebPriceQuoteSource("Yahoo UK",
    "http://uk.finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d3",
    "^([^,]*),.*",  // symbolregexp
    "^[^,]*,([^,]*),.*", // priceregexp
    "^[^,]*,[^,]*,(.*)", // dateregexp
    "%m/%d/%y" // dateformat
  );

  // sl1d1 format for Yahoo France doesn't seem to give a date ever
  // sl1d3 gives us time (99h99) and date
  result["Yahoo France"] = WebPriceQuoteSource("Yahoo France",
    "http://fr.finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d3",
    "([^;]*).*",             // symbolregexp
    "[^;]*.([^;]*),*",       // priceregexp
    "[^;]*.[^;]*...h...([^;]*)", // dateregexp
    "%d/%m/%y"               // dateformat
  );

  result["Globe & Mail"] = WebPriceQuoteSource("Globe & Mail",
    "http://globefunddb.theglobeandmail.com/gishome/plsql/gis.price_history?pi_fund_id=%1",
    TQString(),  // symbolregexp
    "Reinvestment Price \\w+ \\d+, \\d+ (\\d+\\.\\d+)", // priceregexp
    "Reinvestment Price (\\w+ \\d+, \\d+)", // dateregexp
    "%m %d %y" // dateformat
  );

  result["MSN.CA"] = WebPriceQuoteSource("MSN.CA",
    "http://ca.moneycentral.msn.com/investor/quotes/quotes.asp?symbol=%1",
    TQString(),  // symbolregexp
    "Net Asset Value (\\d+\\.\\d+)", // priceregexp
    "NAV update (\\d+\\D+\\d+\\D+\\d+)", // dateregexp
    "%d %m %y" // dateformat
  );
  // Finanztreff (replaces VWD.DE) and boerseonline supplied by Micahel Zimmerman
  result["Finanztreff"] = WebPriceQuoteSource("Finanztreff",
    "http://finanztreff.de/kurse_einzelkurs_detail.htn?u=100&i=%1",
    TQString(),  // symbolregexp
    "([0-9]+,\\d+).+Gattung:Fonds", // priceregexp
    "\\).(\\d+\\D+\\d+\\D+\\d+)", // dateregexp (doesn't work; date in chart
    "%d.%m.%y" // dateformat
  );

  result["boerseonline"] = WebPriceQuoteSource("boerseonline",
    "http://www.boerse-online.de/tools/boerse/einzelkurs_kurse.htm?&s=%1",
    TQString(),  // symbolregexp
    "Akt\\. Kurs.(\\d+,\\d\\d)", // priceregexp
    "Datum.(\\d+\\.\\d+\\.\\d+)", // dateregexp (doesn't work; date in chart
    "%d.%m.%y" // dateformat
  );

  // The following two price sources were contributed by
  // Marc Zahnlecker <tf2k@users.sourceforge.net>

  result["Wallstreet-Online.DE (Default)"] = WebPriceQuoteSource("Wallstreet-Online.DE (Default)",
    "http://www.wallstreet-online.de/si/?k=%1&spid=ws",
    "Symbol:(\\w+)",  // symbolregexp
    "Letzter Kurs: ([0-9.]+,\\d+)", // priceregexp
    ", (\\d+\\D+\\d+\\D+\\d+)", // dateregexp
    "%d %m %y" // dateformat
  );

  // This quote source provided by Peter Lord
  // The trading symbol will normally be the SEDOL (see wikipedia) but
  // the flexibility presently (1/2008) in the code will allow use of
  // the ISIN or MEXID (FT specific) codes
  result["Financial Times UK Funds"] = WebPriceQuoteSource("Financial Times UK Funds",
      "http://funds.ft.com/funds/simpleSearch.do?searchArea=%&search=%1",
      "SEDOL[\\ ]*(\\d+.\\d+)", // symbol regexp
      "\\(GBX\\)[\\ ]*([0-9,]*.\\d+)[\\ ]*", // price regexp
      "Valuation date:[\\ ]*(\\d+/\\d+/\\d+)", // date regexp
      "%d/%m/%y" // date format
  );

  // This quote source provided by Danny Scott
  result["Yahoo Canada"] = WebPriceQuoteSource("Yahoo Canada",
      "http://ca.finance.yahoo.com/q?s=%1",
      "%1", // symbol regexp
      "Last Trade: (\\d+\\.\\d+)", // price regexp
      "day, (.\\D+\\d+\\D+\\d+)", // date regexp
      "%m %d %y" // date format
  );

  // (tf2k) The "mpid" is I think the market place id. In this case five
  // stands for Hamburg.
  //
  // Here the id for several market places: 2 Frankfurt, 3 Berlin, 4
  // Düsseldorf, 5 Hamburg, 6 München/Munich, 7 Hannover, 9 Stuttgart, 10
  // Xetra, 32 NASDAQ, 36 NYSE

  result["Wallstreet-Online.DE (Hamburg)"] = WebPriceQuoteSource("Wallstreet-Online.DE (Hamburg)",
    "http://fonds.wallstreet-online.de/si/?k=%1&spid=ws&mpid=5",
    "Symbol:(\\w+)",  // symbolregexp
    "Fonds \\(EUR\\) ([0-9.]+,\\d+)", // priceregexp
    ", (\\d+\\D+\\d+\\D+\\d+)", // dateregexp
    "%d %m %y" // dateformat
  );

  // The following price quote was contributed by
  // Piotr Adacha <piotr.adacha@googlemail.com>

  // I would like to post new Online Query Settings for KMyMoney. This set is
  // suitable to query stooq.com service, providing quotes for stocks, futures,
  // mutual funds and other financial instruments from Polish Gielda Papierow
  // Wartosciowych (GPW). Unfortunately, none of well-known international
  // services provide quotes for this market (biggest one in central and eastern
  // Europe), thus, I think it could be helpful for Polish users of KMyMoney (and
  // I am one of them for almost a year).

  result["Gielda Papierow Wartosciowych (GPW)"] = WebPriceQuoteSource("Gielda Papierow Wartosciowych (GPW)",
    "http://stooq.com/q/?s=%1",
    TQString(),                   // symbol regexp
    "Kurs.*(\\d+\\.\\d+).*Data",    // price regexp
    "(\\d{4,4}-\\d{2,2}-\\d{2,2})", // date regexp
    "%y %m %d"                   // date format
  );

  // The following price quote is for getting prices of different funds
  // at OMX Baltic market.
  result["OMX Baltic funds"] = WebPriceQuoteSource("OMX Baltic funds",
      "http://www.baltic.omxgroup.com/market/?pg=nontradeddetails&currency=0&instrument=%1",
      TQString(),  // symbolregexp
      "NAV (\\d+,\\d+)",  // priceregexp
      "Kpv (\\d+.\\d+.\\d+)",  // dateregexp
      "%d.%m.%y"   // dateformat
  );

  // The following price quote was contributed by
  // Peter Hargreaves <pete.h@pdh-online.info>
  // The original posting can be found here:
  // http://sourceforge.net/mailarchive/message.php?msg_name=200806060854.11682.pete.h%40pdh-online.info

  // I have PEP and ISA accounts which I invest in Funds with Barclays
  // Stockbrokers. They give me Fund data via Financial Express:
  //
  // https://webfund6.financialexpress.net/Clients/Barclays/default.aspx
  //
  // A typical Fund Factsheet is:
  //
  // https://webfund6.financialexpress.net/Clients/Barclays/search_factsheet_summary.aspx?code=0585239
  //
  // On the Factsheet to identify the fund you can see ISIN Code GB0005852396.
  // In the url, this code is shortened by loosing the first four and last
  // characters.
  //
  // Update:
  //
  // Nick Elliot has contributed a modified regular expression to cope with values presented
  // in pounds as well as those presented in pence. The source can be found here:
  // http://forum.kde.org/update-stock-and-currency-prices-t-32049.html

  result["Financial Express"] = WebPriceQuoteSource("Financial Express",
       "https://webfund6.financialexpress.net/Clients/Barclays/search_factsheet_summary.aspx?code=%1",
       "ISIN Code[^G]*(GB..........).*",  // symbolregexp
       "Current Market Information[^0-9]*([0-9,\\.]+).*", // priceregexp
       "Price Date[^0-9]*(../../....).*", // dateregexp
       "%d/%m/%y"                         // dateformat
  );

  return result;
}

TQStringList WebPriceQuote::quoteSources (const _quoteSystemE _system) {
  if (_system == Native)
    return (quoteSourcesNative());
  else
    return (quoteSourcesFinanceQuote());
}

TQStringList WebPriceQuote::quoteSourcesNative()
{
  TDEConfig *tdeconfig = TDEGlobal::config();
  TQStringList groups = tdeconfig->groupList();

  TQStringList::Iterator it;
  TQRegExp onlineQuoteSource(TQString("^Online-Quote-Source-(.*)$"));

  // get rid of all 'non online quote source' entries
  for(it = groups.begin(); it != groups.end(); it = groups.remove(it)) {
    if(onlineQuoteSource.search(*it) >= 0) {
      // Insert the name part
      groups.insert(it, onlineQuoteSource.cap(1));
    }
  }

  // if the user has the OLD quote source defined, now is the
  // time to remove that entry and convert it to the new system.
  if ( ! groups.count() && tdeconfig->hasGroup("Online Quotes Options") )
  {
    tdeconfig->setGroup("Online Quotes Options");
    TQString url(tdeconfig->readEntry("URL","http://finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d1"));
    TQString symbolRegExp(tdeconfig->readEntry("SymbolRegex","\"([^,\"]*)\",.*"));
    TQString priceRegExp(tdeconfig->readEntry("PriceRegex","[^,]*,([^,]*),.*"));
    TQString dateRegExp(tdeconfig->readEntry("DateRegex","[^,]*,[^,]*,\"([^\"]*)\""));
    tdeconfig->deleteGroup("Online Quotes Options");

    groups += "Old Source";
    tdeconfig->setGroup(TQString("Online-Quote-Source-%1").arg("Old Source"));
    tdeconfig->writeEntry("URL", url);
    tdeconfig->writeEntry("SymbolRegex", symbolRegExp);
    tdeconfig->writeEntry("PriceRegex",priceRegExp);
    tdeconfig->writeEntry("DateRegex", dateRegExp);
    tdeconfig->writeEntry("DateFormatRegex", "%m %d %y");
    tdeconfig->sync();
  }

  // Set up each of the default sources.  These are done piecemeal so that
  // when we add a new source, it's automatically picked up.
  TQMap<TQString,WebPriceQuoteSource> defaults = defaultQuoteSources();
  TQMap<TQString,WebPriceQuoteSource>::const_iterator it_source = defaults.begin();
  while ( it_source != defaults.end() )
  {
    if ( ! groups.contains( (*it_source).m_name ) )
    {
      groups += (*it_source).m_name;
      (*it_source).write();
      tdeconfig->sync();
    }
    ++it_source;
  }

  return groups;
}

TQStringList WebPriceQuote::quoteSourcesFinanceQuote()
{
  if (m_financeQuoteSources.empty()) { // run the process one time only
    FinanceQuoteProcess getList;
    m_financeQuoteScriptPath =
        TDEGlobal::dirs()->findResource("appdata", TQString("misc/financequote.pl"));
    getList.launch( m_financeQuoteScriptPath );
    while (!getList.isFinished()) {
      tqApp->processEvents();
    }
    m_financeQuoteSources = getList.getSourceList();
  }
  return (m_financeQuoteSources);
}

//
// Helper class to load/save an individual source
//

WebPriceQuoteSource::WebPriceQuoteSource(const TQString& name, const TQString& url, const TQString& sym, const TQString& price, const TQString& date, const TQString& dateformat):
  m_name(name),
  m_url(url),
  m_sym(sym),
  m_price(price),
  m_date(date),
  m_dateformat(dateformat)
{
}

WebPriceQuoteSource::WebPriceQuoteSource(const TQString& name)
{
  m_name = name;
  TDEConfig *tdeconfig = TDEGlobal::config();
  tdeconfig->setGroup(TQString("Online-Quote-Source-%1").arg(m_name));
  m_sym = tdeconfig->readEntry("SymbolRegex");
  m_date = tdeconfig->readEntry("DateRegex");
  m_dateformat = tdeconfig->readEntry("DateFormatRegex","%m %d %y");
  m_price = tdeconfig->readEntry("PriceRegex");
  m_url = tdeconfig->readEntry("URL");
  m_skipStripping = tdeconfig->readBoolEntry("SkipStripping", false);
}

void WebPriceQuoteSource::write(void) const
{
  TDEConfig *tdeconfig = TDEGlobal::config();
  tdeconfig->setGroup(TQString("Online-Quote-Source-%1").arg(m_name));
  tdeconfig->writeEntry("URL", m_url);
  tdeconfig->writeEntry("PriceRegex", m_price);
  tdeconfig->writeEntry("DateRegex", m_date);
  tdeconfig->writeEntry("DateFormatRegex", m_dateformat);
  tdeconfig->writeEntry("SymbolRegex", m_sym);
  if(m_skipStripping)
    tdeconfig->writeEntry("SkipStripping", m_skipStripping);
  else
    tdeconfig->deleteEntry("SkipStripping");
}

void WebPriceQuoteSource::rename(const TQString& name)
{
  remove();
  m_name = name;
  write();
}

void WebPriceQuoteSource::remove(void) const
{
  TDEConfig *tdeconfig = TDEGlobal::config();
  tdeconfig->deleteGroup(TQString("Online-Quote-Source-%1").arg(m_name));
}

//
// Helper class to babysit the TDEProcess used for running the local script in that case
//

WebPriceQuoteProcess::WebPriceQuoteProcess(void)
{
  connect(this, TQT_SIGNAL(receivedStdout(TDEProcess*, char*, int)), this, TQT_SLOT(slotReceivedDataFromFilter(TDEProcess*, char*, int)));
  connect(this, TQT_SIGNAL(processExited(TDEProcess*)), this, TQT_SLOT(slotProcessExited(TDEProcess*)));
}

void WebPriceQuoteProcess::slotReceivedDataFromFilter(TDEProcess* /*_process*/, char* _pcbuffer, int _nbufferlen)
{
  TQByteArray data;
  data.duplicate(_pcbuffer, _nbufferlen);

//   kdDebug(2) << "WebPriceQuoteProcess::slotReceivedDataFromFilter(): " << TQString(data) << endl;
  m_string += TQString(data);
}

void WebPriceQuoteProcess::slotProcessExited(TDEProcess*)
{
//   kdDebug(2) << "WebPriceQuoteProcess::slotProcessExited()" << endl;
  emit processExited(m_string);
  m_string.truncate(0);
}

//
// Helper class to babysit the TDEProcess used for running the Finance Quote sources script
//

FinanceQuoteProcess::FinanceQuoteProcess(void)
{
  m_isDone = false;
  m_string = "";
  m_fqNames["aex"] = "AEX";
  m_fqNames["aex_futures"] = "AEX Futures";
  m_fqNames["aex_options"] = "AEX Options";
  m_fqNames["amfiindia"] = "AMFI India";
  m_fqNames["asegr"] = "ASE";
  m_fqNames["asia"] = "Asia (Yahoo, ...)";
  m_fqNames["asx"] = "ASX";
  m_fqNames["australia"] = "Australia (ASX, Yahoo, ...)";
  m_fqNames["bmonesbittburns"] = "BMO NesbittBurns";
  m_fqNames["brasil"] = "Brasil (Yahoo, ...)";
  m_fqNames["canada"] = "Canada (Yahoo, ...)";
  m_fqNames["canadamutual"] = "Canada Mutual (Fund Library, ...)";
  m_fqNames["deka"] = "Deka Investments";
  m_fqNames["dutch"] = "Dutch (AEX, ...)";
  m_fqNames["dwsfunds"] = "DWS";
  m_fqNames["europe"] = "Europe (Yahoo, ...)";
  m_fqNames["fidelity"] = "Fidelity (Fidelity, ...)";
  m_fqNames["fidelity_direct"] = "Fidelity Direct";
  m_fqNames["financecanada"] = "Finance Canada";
  m_fqNames["ftportfolios"] = "First Trust (First Trust, ...)";
  m_fqNames["ftportfolios_direct"] = "First Trust Portfolios";
  m_fqNames["fundlibrary"] = "Fund Library";
  m_fqNames["greece"] = "Greece (ASE, ...)";
  m_fqNames["indiamutual"] = "India Mutual (AMFI, ...)";
  m_fqNames["maninv"] = "Man Investments";
  m_fqNames["fool"] = "Motley Fool";
  m_fqNames["nasdaq"] = "Nasdaq (Yahoo, ...)";
  m_fqNames["nz"] = "New Zealand (Yahoo, ...)";
  m_fqNames["nyse"] = "NYSE (Yahoo, ...)";
  m_fqNames["nzx"] = "NZX";
  m_fqNames["platinum"] = "Platinum Asset Management";
  m_fqNames["seb_funds"] = "SEB";
  m_fqNames["sharenet"] = "Sharenet";
  m_fqNames["za"] = "South Africa (Sharenet, ...)";
  m_fqNames["troweprice_direct"] = "T. Rowe Price";
  m_fqNames["troweprice"] = "T. Rowe Price";
  m_fqNames["tdefunds"] = "TD Efunds";
  m_fqNames["tdwaterhouse"] = "TD Waterhouse Canada";
  m_fqNames["tiaacref"] = "TIAA-CREF";
  m_fqNames["trustnet"] = "Trustnet";
  m_fqNames["uk_unit_trusts"] = "U.K. Unit Trusts";
  m_fqNames["unionfunds"] = "Union Investments";
  m_fqNames["tsp"] = "US Govt. Thrift Savings Plan";
  m_fqNames["usfedbonds"] = "US Treasury Bonds";
  m_fqNames["usa"] = "USA (Yahoo, Fool ...)";
  m_fqNames["vanguard"] = "Vanguard";
  m_fqNames["vwd"] = "VWD";
  m_fqNames["yahoo"] = "Yahoo";
  m_fqNames["yahoo_asia"] = "Yahoo Asia";
  m_fqNames["yahoo_australia"] = "Yahoo Australia";
  m_fqNames["yahoo_brasil"] = "Yahoo Brasil";
  m_fqNames["yahoo_europe"] = "Yahoo Europe";
  m_fqNames["yahoo_nz"] = "Yahoo New Zealand";
  m_fqNames["zifunds"] = "Zuerich Investments";
  connect(this, TQT_SIGNAL(receivedStdout(TDEProcess*, char*, int)), this, TQT_SLOT(slotReceivedDataFromFilter(TDEProcess*, char*, int)));
  connect(this, TQT_SIGNAL(processExited(TDEProcess*)), this, TQT_SLOT(slotProcessExited(TDEProcess*)));
}

void FinanceQuoteProcess::slotReceivedDataFromFilter(TDEProcess* /*_process*/, char* _pcbuffer, int _nbufferlen)
{
  TQByteArray data;
  data.duplicate(_pcbuffer, _nbufferlen);

//   kdDebug(2) << "WebPriceQuoteProcess::slotReceivedDataFromFilter(): " << TQString(data) << endl;
  m_string += TQString(data);
}

void FinanceQuoteProcess::slotProcessExited(TDEProcess*)
{
//   kdDebug(2) << "WebPriceQuoteProcess::slotProcessExited()" << endl;
  m_isDone = true;
}

void FinanceQuoteProcess::launch (const TQString& scriptPath) {
  clearArguments();
  arguments.append(TQCString("perl"));
  arguments.append (TQCString(scriptPath));
  arguments.append (TQCString("-l"));
  if (!start(TDEProcess::NotifyOnExit, TDEProcess::Stdout)) tqFatal ("Unable to start FQ script");
  return;
}

TQStringList FinanceQuoteProcess::getSourceList() {
  TQStringList raw = TQStringList::split(0x0A, m_string);
  TQStringList sources;
  TQStringList::iterator it;
  for (it = raw.begin(); it != raw.end(); ++it) {
    if (m_fqNames[*it].isEmpty()) sources.append(*it);
    else sources.append(m_fqNames[*it]);
  }
  sources.sort();
  return (sources);
}

const TQString FinanceQuoteProcess::crypticName(const TQString& niceName) {
  TQString ret (niceName);
  fqNameMap::iterator it;
  for (it = m_fqNames.begin(); it != m_fqNames.end(); ++it) {
    if (niceName == it.data()) {
      ret = it.key();
      break;
    }
  }
  return (ret);
}

const TQString FinanceQuoteProcess::niceName(const TQString& crypticName) {
  TQString ret (m_fqNames[crypticName]);
  if (ret.isEmpty()) ret = crypticName;
  return (ret);
}
//
// Universal date converter
//

// In 'strict' mode, this is designed to be compatable with the QIF profile date
// converter.  However, that converter deals with the concept of an apostrophe
// format in a way I don't understand.  So for the moment, they are 99%
// compatable, waiting on that issue. (acejones)

TQDate MyMoneyDateFormat::convertString(const TQString& _in, bool _strict, unsigned _centurymidpoint) const
{
  //
  // Break date format string into component parts
  //

  TQRegExp formatrex("%([mdy]+)(\\W+)%([mdy]+)(\\W+)%([mdy]+)",false /* case sensitive */);
  if ( formatrex.search(m_format) == -1 )
  {
    throw new MYMONEYEXCEPTION("Invalid format string");
  }

  TQStringList formatParts;
  formatParts += formatrex.cap(1);
  formatParts += formatrex.cap(3);
  formatParts += formatrex.cap(5);

  TQStringList formatDelimiters;
  formatDelimiters += formatrex.cap(2);
  formatDelimiters += formatrex.cap(4);

  //
  // Break input string up into component parts,
  // using the delimiters found in the format string
  //

  TQRegExp inputrex;
  inputrex.setCaseSensitive(false);

  // strict mode means we must enforce the delimiters as specified in the
  // format.  non-strict allows any delimiters
  if ( _strict )
    inputrex.setPattern(TQString("(\\w+)%1(\\w+)%2(\\w+)").arg(formatDelimiters[0],formatDelimiters[1]));
  else
    inputrex.setPattern("(\\w+)\\W+(\\w+)\\W+(\\w+)");

  if ( inputrex.search(_in) == -1 )
  {
    throw new MYMONEYEXCEPTION("Invalid input string");
  }

  TQStringList scannedParts;
  scannedParts += inputrex.cap(1).lower();
  scannedParts += inputrex.cap(2).lower();
  scannedParts += inputrex.cap(3).lower();

  //
  // Convert the scanned parts into actual date components
  //

  unsigned day = 0, month = 0, year = 0;
  bool ok;
  TQRegExp digitrex("(\\d+)");
  TQStringList::const_iterator it_scanned = scannedParts.begin();
  TQStringList::const_iterator it_format = formatParts.begin();
  while ( it_scanned != scannedParts.end() )
  {
    switch ( (*it_format)[0] )
    {
    case 'd':
      // remove any extraneous non-digits (e.g. read "3rd" as 3)
      ok = false;
      if ( digitrex.search(*it_scanned) != -1 )
        day = digitrex.cap(1).toUInt(&ok);
      if ( !ok || day > 31 )
        throw new MYMONEYEXCEPTION(TQString("Invalid day entry: %1").arg(*it_scanned));
      break;
    case 'm':
      month = (*it_scanned).toUInt(&ok);
      if ( !ok )
      {
        // maybe it's a textual date
        unsigned i = 1;
        while ( i <= 12 )
        {
          if(TDEGlobal::locale()->calendar()->monthName(i, 2000, true).lower() == *it_scanned
          || TDEGlobal::locale()->calendar()->monthName(i, 2000, false).lower() == *it_scanned)
            month = i;
          ++i;
        }
      }

      if ( month < 1 || month > 12 )
        throw new MYMONEYEXCEPTION(TQString("Invalid month entry: %1").arg(*it_scanned));

      break;
    case 'y':
      if ( _strict && (*it_scanned).length() != (*it_format).length())
        throw new MYMONEYEXCEPTION(TQString("Length of year (%1) does not match expected length (%2).")
                .arg(*it_scanned,*it_format));

      year = (*it_scanned).toUInt(&ok);

      if (!ok)
        throw new MYMONEYEXCEPTION(TQString("Invalid year entry: %1").arg(*it_scanned));

      //
      // 2-digit year case
      //
      // this algorithm will pick a year within +/- 50 years of the
      // centurymidpoint parameter.  i.e. if the midpoint is 2000,
      // then 0-49 will become 2000-2049, and 50-99 will become 1950-1999
      if ( year < 100 )
      {
        unsigned centuryend = _centurymidpoint + 50;
        unsigned centurybegin = _centurymidpoint - 50;

        if ( year < centuryend % 100 )
          year += 100;
        year += centurybegin - centurybegin % 100;
      }

      if ( year < 1900 )
        throw new MYMONEYEXCEPTION(TQString("Invalid year (%1)").arg(year));

      break;
    default:
      throw new MYMONEYEXCEPTION("Invalid format character");
    }

    ++it_scanned;
    ++it_format;
  }

  TQDate result(year,month,day);
  if ( ! result.isValid() )
    throw new MYMONEYEXCEPTION(TQString("Invalid date (yr%1 mo%2 dy%3)").arg(year).arg(month).arg(day));

  return result;
}

//
// Unit test helpers
//

convertertest::QuoteReceiver::QuoteReceiver(WebPriceQuote* q, TQObject* parent, const char *name) :
  TQObject(parent,name)
{
  connect(q,TQT_SIGNAL(quote(const TQString&,const TQDate&, const double&)),
    this,TQT_SLOT(slotGetQuote(const TQString&,const TQDate&, const double&)));
  connect(q,TQT_SIGNAL(status(const TQString&)),
    this,TQT_SLOT(slotStatus(const TQString&)));
  connect(q,TQT_SIGNAL(error(const TQString&)),
    this,TQT_SLOT(slotError(const TQString&)));
}

convertertest::QuoteReceiver::~QuoteReceiver()
{
}

void convertertest::QuoteReceiver::slotGetQuote(const TQString&,const TQDate& d, const double& m)
{
//   kdDebug(2) << "test::QuoteReceiver::slotGetQuote( , " << d << " , " << m.toString() << " )" << endl;

  m_price = MyMoneyMoney(m);
  m_date = d;
}
void convertertest::QuoteReceiver::slotStatus(const TQString& msg)
{
//   kdDebug(2) << "test::QuoteReceiver::slotStatus( " << msg << " )" << endl;

  m_statuses += msg;
}
void convertertest::QuoteReceiver::slotError(const TQString& msg)
{
//   kdDebug(2) << "test::QuoteReceiver::slotError( " << msg << " )" << endl;

  m_errors += msg;
}

// vim:cin:si:ai:et:ts=2:sw=2:

#include "webpricequote.moc"
