/***************************************************************************
                             ofxpartner.cpp
                             ----------
    begin                : Fri Jan 23 2009
    copyright            : (C) 2009 by Thomas Baumgart
    email                : Thomas Baumgart <ipwizard@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.                                   *
 *                                                                         *
 ***************************************************************************/


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <unistd.h>

// ----------------------------------------------------------------------------
// QT Includes

#include <tqdatetime.h>
#include <tqeventloop.h>
#include <tqfileinfo.h>
#include <tqvaluelist.h>
#include <tqapplication.h>
#include <tqdom.h>
#include <tqregexp.h>
#include <tqdir.h>
#include <tqtextstream.h>

// ----------------------------------------------------------------------------
// KDE Includes


#include <tdeio/job.h>
#include <tdelocale.h>
#include <tdemessagebox.h>

// ----------------------------------------------------------------------------
// Project Includes

#include "ofxpartner.h"

namespace OfxPartner
{
bool post(const TQString& request, const TQMap<TQString, TQString>& attr, const KURL& url, const KURL& filename);
bool get(const TQString& request, const TQMap<TQString, TQString>& attr, const KURL& url, const KURL& filename);

const TQString kBankFilename = "ofx-bank-index.xml";
const TQString kCcFilename = "ofx-cc-index.xml";
const TQString kInvFilename = "ofx-inv-index.xml";

#define VER "9"

static TQString directory;

void setDirectory(const TQString& dir)
{
  directory = dir;
}

bool needReload(const TQFileInfo& i)
{
  return ((!i.isReadable())
  || (i.lastModified().addDays(7) < TQDateTime::currentDateTime())
  || (i.size() < 1024));
}

void ValidateIndexCache(void)
{
  // TODO (Ace) Check whether these files exist and are recent enough before getting them again

  struct stat filestats;
  KURL fname;

  TQMap<TQString, TQString> attr;
  attr["content-type"] = "application/x-www-form-urlencoded";
  attr["accept"] = "*/*";

  fname = directory + kBankFilename;
  TQFileInfo i(fname.path());
  if(needReload(i))
    post("T=1&S=*&R=1&O=0&TEST=0", attr, KURL("http://moneycentral.msn.com/money/2005/mnynet/service/ols/filist.aspx?SKU=3&VER=" VER), fname);

  fname = directory + kCcFilename;
  i = TQFileInfo(fname.path());
  if(needReload(i))
    post("T=2&S=*&R=1&O=0&TEST=0", attr, KURL("http://moneycentral.msn.com/money/2005/mnynet/service/ols/filist.aspx?SKU=3&VER=" VER) ,fname);

  fname = directory + kInvFilename;
  i = TQFileInfo(fname.path());
  if(needReload(i))
    post("T=3&S=*&R=1&O=0&TEST=0", attr, KURL("http://moneycentral.msn.com/money/2005/mnynet/service/ols/filist.aspx?SKU=3&VER=" VER), fname);
}

static void ParseFile(TQMap<TQString, TQString>& result, const TQString& fileName, const TQString& bankName)
{
  TQFile f(fileName);
  if(f.open(IO_ReadOnly)) {
    TQTextStream stream(&f);
    stream.setEncoding(TQTextStream::Unicode);
    TQString msg;
    int errl, errc;
    TQDomDocument doc;
    if(doc.setContent(stream.read(), &msg, &errl, &errc)) {
      TQDomNodeList olist = doc.elementsByTagName("prov");
      for(int i = 0; i < olist.count(); ++i) {
        TQDomNode onode = olist.item(i);
        if(onode.isElement()) {
          bool collectGuid = false;
          TQDomElement elo = onode.toElement();
          TQDomNodeList ilist = onode.childNodes();
          for(int j = 0; j < ilist.count(); ++j) {
            TQDomNode inode = ilist.item(j);
            TQDomElement el = inode.toElement();
            if(el.tagName() == "name") {
              if(bankName.isEmpty())
                result[el.text()] = TQString();
              else if(el.text() == bankName) {
                collectGuid = true;
              }
            }
            if(el.tagName() == "guid" && collectGuid) {
              result[el.text()] = TQString();
            }
          }
        }
      }
    }
    f.close();
  }
}

TQValueList<TQString> BankNames(void)
{
  TQMap<TQString, TQString> result;

  // Make sure the index files are up to date
  ValidateIndexCache();

  ParseFile(result, directory + kBankFilename, TQString());
  ParseFile(result, directory + kCcFilename, TQString());
  ParseFile(result, directory + kInvFilename, TQString());

  // Add Innovision
  result["Innovision"] = TQString();

  return result.keys();
}

TQValueList<TQString> FipidForBank(const TQString& bank)
{
  TQMap<TQString, TQString> result;

  ParseFile(result, directory + kBankFilename, bank);
  ParseFile(result, directory + kCcFilename, bank);
  ParseFile(result, directory + kInvFilename, bank);

  // the fipid for Innovision is 1.
  if ( bank == "Innovision" )
    result["1"] = TQString();

  return result.keys();
}

TQString extractNodeText(TQDomElement& node, const TQString& name)
{
  TQString res;
  TQRegExp exp("([^/]+)/?([^/].*)?");
  if(exp.search(name) != -1) {
    TQDomNodeList olist = node.elementsByTagName(exp.cap(1));
    if(olist.count()) {
      TQDomNode onode = olist.item(0);
      if(onode.isElement()) {
        TQDomElement elo = onode.toElement();
        if(exp.cap(2).isEmpty()) {
          res = elo.text();
        } else {
          res = extractNodeText(elo, exp.cap(2));
        }
      }
    }
  }
  return res;
}

TQString extractNodeText(TQDomDocument& doc, const TQString& name)
{
  TQString res;
  TQRegExp exp("([^/]+)/?([^/].*)?");
  if(exp.search(name) != -1) {
    TQDomNodeList olist = doc.elementsByTagName(exp.cap(1));
    if(olist.count()) {
      TQDomNode onode = olist.item(0);
      if(onode.isElement()) {
        TQDomElement elo = onode.toElement();
        if(exp.cap(2).isEmpty()) {
          res = elo.text();
        } else {
          res = extractNodeText(elo, exp.cap(2));
        }
      }
    }
  }
  return res;
}

OfxFiServiceInfo ServiceInfo(const TQString& fipid)
{
  OfxFiServiceInfo result;
  memset(&result, 0, sizeof(OfxFiServiceInfo));

  // Hard-coded values for Innovision test server
  if ( fipid == "1" )
  {
    strncpy(result.fid,"00000",OFX_FID_LENGTH-1);
    strncpy(result.org,"ReferenceFI",OFX_ORG_LENGTH-1);
    strncpy(result.url,"http://ofx.innovision.com",OFX_URL_LENGTH-1);
    result.accountlist = 1;
    result.statements = 1;
    result.billpay = 1;
    result.investments = 1;

    return result;
  }

  TQMap<TQString, TQString> attr;
  attr["content-type"] = "application/x-www-form-urlencoded";
  attr["accept"] = "*/*";

  KURL guidFile(TQString("%1fipid-%2.xml").arg(directory).arg(fipid));

  // Apparently at some point in time, for VER=6 msn returned an online URL
  // to a static error page (http://moneycentral.msn.com/cust404.htm).
  // Increasing to VER=9 solved the problem. This may happen again in the
  // future.
  TQFileInfo i(guidFile.path());
  if(!i.isReadable() || i.lastModified().addDays(7) < TQDateTime::currentDateTime())
    get("", attr, KURL(TQString("http://moneycentral.msn.com/money/2005/mnynet/service/olsvcupd/OnlSvcBrandInfo.aspx?MSNGUID=&GUID=%1&SKU=3&VER=" VER).arg(fipid)), guidFile);

  TQFile f(guidFile.path());
  if(f.open(IO_ReadOnly)) {
    TQTextStream stream(&f);
    stream.setEncoding(TQTextStream::Unicode);
    TQString msg;
    int errl, errc;
    TQDomDocument doc;
    if(doc.setContent(stream.read(), &msg, &errl, &errc)) {
      TQString fid = extractNodeText(doc, "ProviderSettings/FID");
      TQString org = extractNodeText(doc, "ProviderSettings/Org");
      TQString url = extractNodeText(doc, "ProviderSettings/ProviderURL");
      strncpy(result.fid, fid.latin1(), OFX_FID_LENGTH-1);
      strncpy(result.org, org.latin1(), OFX_ORG_LENGTH-1);
      strncpy(result.url, url.latin1(), OFX_URL_LENGTH-1);
      result.accountlist = (extractNodeText(doc, "ProviderSettings/AcctListAvail") == "1");
      result.statements = (extractNodeText(doc, "BankingCapabilities/Bank") == "1");
      result.billpay= (extractNodeText(doc, "BillPayCapabilities/Pay") == "1");
      result.investments= (extractNodeText(doc, "InvestmentCapabilities/BrkStmt") == "1");
    }
  }

  return result;
}

bool get(const TQString& request, const TQMap<TQString, TQString>& attr, const KURL& url, const KURL& filename)
{
  TQByteArray req(0);
  OfxHttpRequest job("GET", url, req, attr, filename, true);

  return job.error() == TQHttp::NoError;
}

bool post(const TQString& request, const TQMap<TQString, TQString>& attr, const KURL& url, const KURL& filename)
{
  TQByteArray req;
  req.fill(0, request.length()+1);
  req.duplicate(request.ascii(), request.length());

  OfxHttpRequest job("POST", url, req, attr, filename, true);
  return job.error() == TQHttp::NoError;
}

} // namespace OfxPartner

class OfxHttpsRequest::Private
{
public:
  TQFile		m_fpTrace;
};

OfxHttpsRequest::OfxHttpsRequest(const TQString& type, const KURL &url, const TQByteArray &postData, const TQMap<TQString, TQString>& metaData, const KURL& dst, bool showProgressInfo) :
  d(new Private),
  m_dst(dst)
{
  TQDir homeDir(TQDir::home());
  if(homeDir.exists("ofxlog.txt")) {
    d->m_fpTrace.setName(TQString("%1/ofxlog.txt").arg(TQDir::homeDirPath()));
    d->m_fpTrace.open(IO_WriteOnly | IO_Append);
  }

  m_job = TDEIO::http_post(url, postData, showProgressInfo);
  m_job->addMetaData("content-type", "Content-type: application/x-ofx" );

  if(d->m_fpTrace.isOpen()) {
    TQTextStream ts(&d->m_fpTrace);
    ts << "url: " << url.prettyURL() << "\n";
    ts << "request:\n" << TQString(postData) << "\n" << "response:\n";
  }

  connect(m_job,TQT_SIGNAL(result(TDEIO::Job*)),this,TQT_SLOT(slotOfxFinished(TDEIO::Job*)));
  connect(m_job,TQT_SIGNAL(data(TDEIO::Job*, const TQByteArray&)),this,TQT_SLOT(slotOfxData(TDEIO::Job*,const TQByteArray&)));
  connect(m_job,TQT_SIGNAL(connected(TDEIO::Job*)),this,TQT_SLOT(slotOfxConnected(TDEIO::Job*)));

  tqApp->enter_loop();
}

OfxHttpsRequest::~OfxHttpsRequest()
{
  if(d->m_fpTrace.isOpen()) {
    d->m_fpTrace.close();
  }
}

void OfxHttpsRequest::slotOfxConnected(TDEIO::Job*)
{
  m_file.setName(m_dst.path());
  m_file.open(IO_WriteOnly);
}

void OfxHttpsRequest::slotOfxData(TDEIO::Job*,const TQByteArray& _ba)
{
  if(m_file.isOpen()) {
    TQTextStream ts(&m_file);
    ts << TQString(_ba);

    if(d->m_fpTrace.isOpen()) {
      d->m_fpTrace.writeBlock(_ba, _ba.size());
    }


  }
}

void OfxHttpsRequest::slotOfxFinished(TDEIO::Job* /* e */)
{
  if(m_file.isOpen()) {
    m_file.close();
    if(d->m_fpTrace.isOpen()) {
      d->m_fpTrace.writeBlock("\nCompleted\n\n\n\n", 14);
    }
  }

  int error = m_job->error();
  if ( error ) {
    m_job->showErrorDialog();
    unlink(m_dst.path().local8Bit());

  } else if ( m_job->isErrorPage() ) {
    TQString details;
    TQFile f( m_dst.path() );
    if ( f.open( IO_ReadOnly ) ) {
      TQTextStream stream( &f );
      TQString line;
      while ( !stream.atEnd() ) {
        details += stream.readLine(); // line of text excluding '\n'
      }
      f.close();
    }
    KMessageBox::detailedSorry( 0, i18n("The HTTP request failed."), details, i18n("Failed") );
    unlink(m_dst.path().local8Bit());
  }

  tqApp->exit_loop();
}



OfxHttpRequest::OfxHttpRequest(const TQString& type, const KURL &url, const TQByteArray &postData, const TQMap<TQString, TQString>& metaData, const KURL& dst, bool showProgressInfo)
{
  TQFile f(dst.path());
  m_error = TQHttp::NoError;
  TQString errorMsg;
  if(f.open(IO_WriteOnly)) {
    m_job = new TQHttp(url.host());
    TQHttpRequestHeader header(type, url.encodedPathAndQuery());
    header.setValue("Host", url.host());
    TQMap<TQString, TQString>::const_iterator it;
    for(it = metaData.begin(); it != metaData.end(); ++it) {
      header.setValue(it.key(), *it);
    }

    m_job->request(header, postData, TQT_TQIODEVICE(&f));

    connect(m_job, TQT_SIGNAL(requestFinished(int, bool)),
            this, TQT_SLOT(slotOfxFinished(int, bool)));

    tqApp->enter_loop();

    if(m_error != TQHttp::NoError)
      errorMsg = m_job->errorString();

    delete m_job;
  } else {
    m_error = TQHttp::Aborted;
    errorMsg = i18n("Cannot open file %1 for writing").arg(dst.path());
  }

  if(m_error != TQHttp::NoError) {
    KMessageBox::error(0, errorMsg, i18n("OFX setup error"));
    unlink(dst.path().local8Bit());
  }
}

void OfxHttpRequest::slotOfxFinished(int, bool rc)
{
  if(rc) {
    m_error = m_job->error();
  }
  tqApp->exit_loop();
}

#include "ofxpartner.moc"
