#include "sockkanjiclient.h"
#include "config.h"
#include <qapplication.h>
#include <qmessagebox.h>
#include <qcstring.h>
#include <qdatastream.h>
#include <qtextcodec.h>
#include <qsocket.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>

const QString CANNA_AF_UNIX_PATH = "/tmp/.iroha_unix/IROHA";


SockKanjiClient::SockKanjiClient()
{
  _contxt = 0;
  _nbunsetu = 0;
  _convertflag = FALSE;
  _sock = new QSocket();
  _diclist = QStringList();
  connect(_sock, SIGNAL(connected()), this, SLOT(slotConnected()));
  connect(_sock, SIGNAL(error(int)), this, SLOT(slotDetectError()));
  //connect(_sock, SIGNAL(readyRead()), this, SLOT(slotInitializeReply())); // temporary
  connect(_sock, SIGNAL(connectionClosed()), this, SLOT(slotConnectionClosed()));
  connect(_sock, SIGNAL(readyRead()), this, SLOT(slotDispatch()));
}


SockKanjiClient::~SockKanjiClient()
{
  delete _sock;
}


// connect to Kanji server
void 
SockKanjiClient::init() 
{
  _sock->close();

  if ( Config::readBoolEntry("_chkremote", FALSE) ) {
    // INET Domain
    qDebug("INET Domain socket");
    _sock->connectToHost(Config::readEntry("_edtsvrname", "localhost"), 
			 Config::readEntry("_edtport", "0").toUInt());

  } else {
    // UNIX Domain
    qDebug("UNIX Domain socket");

    int    fd;
    struct sockaddr_un  addr;
    
    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
      QMessageBox::critical(0, "Kanji Server Error", 
			    "Socket Create Error",
			    QMessageBox::Ok | QMessageBox::Default, 0);
      return;
    }
    
    memset((char *)&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, CANNA_AF_UNIX_PATH.latin1());
 
    // connect
    if (::connect(fd, (sockaddr *)&addr, sizeof(addr)) < 0){
      QMessageBox::critical(0, "Kanji Server Error", 
			    "Socket Connect Error",
			    QMessageBox::Ok | QMessageBox::Default, 0);
      ::close(fd);
      return;
    }
    
    _sock->setSocket(fd);  // UNIX Domain socket set
    slotConnected();
  }
}


void 
SockKanjiClient::slotConnected() 
{
  qDebug("socket: %d", _sock->socket());
  sendInitialize();
}


void 
SockKanjiClient::slotDetectError() 
{
  QMessageBox::critical(0, "Kanji Server Error", 
			"Socket Error!\nConnects to localhost by UNIX domain.",
			QMessageBox::Ok | QMessageBox::Default, 0);
  
  Config::writeEntry("_chkremote", FALSE);
  init();
}


void
SockKanjiClient::slotConnectionClosed()
{
  QMessageBox::critical(0, "Kanji Server Connection Closed",
			"Socket Connection Closed!\nConnects to localhost by UNIX domain.",
			QMessageBox::Ok | QMessageBox::Default, 0);
  
  Config::writeEntry("_chkremote", FALSE);
  init();
}


void 
SockKanjiClient::sendInitialize()
{
  QByteArray  pack;
  QDataStream ds(pack, IO_WriteOnly);
  ds.setByteOrder(QDataStream::BigEndian);
  
  QCString user("3.0:");
  user += QCString(getenv("USER"));
  ds << 1L << 0L;
  ds.writeRawBytes(user.data(), user.length());

  _sock->writeBlock(pack.data(), pack.size());
}


void 
SockKanjiClient::recvInitializeReply()
{
  uint len;
  if ((len = _sock->size()) != 4) {
    qWarning("Server response incorrect.  %s", __FUNCTION__);
  } 
  
  QByteArray respack(len);
  QDataStream ds(respack, IO_ReadOnly);
  ds.setByteOrder(QDataStream::BigEndian);

  // response packet recv
  if (_sock->readBlock(respack.data(), len) < 0) {
    qFatal("Recv error.  %s", __FUNCTION__);
  }
  
  int res;
  ds >> res;
  if (res == -1 || res == -2) {
    //qFatal("Protocal version error.  %s", __FUNCTION__);
    int ret = QMessageBox::critical(0, "Protocal version error", 
				    "Protocal version error",
				    QMessageBox::Ok | QMessageBox::Default, 0);
    if (ret) {
      qApp->quit();
    }
  } 

  QDataStream dst(respack, IO_ReadOnly);
  dst.setByteOrder(QDataStream::BigEndian);
  ushort  minor;
  dst >> minor >> _contxt;
  qDebug("minor version: %d  context: %d", minor, _contxt);

  getDictionaryList();  
}


void
SockKanjiClient::slotDispatch()
{
  if (_sock->bytesAvailable() == 4) {
    recvInitializeReply();
    return;
  }

  QByteArray pack(2);
  if (_sock->readBlock(pack.data(), 2) < 0) {
    qFatal("Recv error.  %s", __FUNCTION__);
  }
  
  switch (pack.at(0)) {
  case 0x06:
    recvGetDictionaryListReply();
    break;

  case 0x08:
    recvMountDictionaryReply();
    break;

  case 0x0f:
    recvBeginConvertReply();
    break;

  case 0x10:
    recvEndConvertReply();
    break;

  case 0x11:
    recvGetCandidacyListReply();
    break;
    
  case 0x12:
    recvGetYomiReply();
    break;

  case 0x1a:
    recvResizePause();
    break;
    
  default:
    qDebug("unsupported:%d", pack.at(0)); 
    break;
  }
}



void 
SockKanjiClient::getDictionaryList()
{
  QByteArray  pack;
  QDataStream ds(pack, IO_ReadWrite);
  ds.setByteOrder(QDataStream::BigEndian);
  
  ds << (uchar)0x06 << (uchar)0 << (ushort)4 << _contxt << (ushort)4096;
  _sock->writeBlock(pack.data(), pack.size());
}


void 
SockKanjiClient::recvGetDictionaryListReply()
{
  uint datalen = _sock->size();
  QByteArray data(datalen);
  if (_sock->readBlock(data.data(), datalen) < 0) {
    qFatal("Recv error.  %s", __FUNCTION__);
  }

  QDataStream ds(data, IO_ReadOnly);
  ds.setByteOrder(QDataStream::BigEndian);

  short len, ndic;
  ds >> len >> ndic;
  
  if (len != datalen - 2) {
    qWarning("Server response incorrect.  %s", __FUNCTION__);
  } 
  
  qDebug("number of dictionary: %d", ndic);
  
  QString dic;
  for (int i = 0; i < ndic; i++) {
    dic = QString();
    Q_INT8 s;
    while (1) {
      ds >> s;
      if ( !s ) break;
      dic += s;
    }
    _diclist.append(dic);
  }

  mountDictionary();
}


void 
SockKanjiClient::mountDictionary()
{
  if ( _diclist.isEmpty() ) return;

  QByteArray  pack;
  QDataStream ds(pack, IO_ReadWrite);
  ds.setByteOrder(QDataStream::BigEndian);
  
  QString dic = _diclist.first();
  _diclist.pop_front();
  
  if (dic == "pub") {    // Dosen't mount "pub" dictionary
    if (_diclist.isEmpty()) return;
    dic = _diclist.first();
    _diclist.pop_front();
  }
  
  qDebug("request mount dictionary name: %s", dic.data());    
  ds << (uchar)0x08 << (uchar)0 << (ushort)(dic.length()+7) << 0L << _contxt;
  ds.writeRawBytes(dic.latin1(), dic.length());
  ds << (uchar)0;
  
  _sock->writeBlock(pack.data(), pack.size());
}


void 
SockKanjiClient::recvMountDictionaryReply()
{
  uint datalen = _sock->size();
  QByteArray data(datalen);
  if (_sock->readBlock(data.data(), datalen) < 0) {
    qFatal("Recv error.  %s", __FUNCTION__);
  }

  QDataStream ds(data, IO_ReadOnly);
  ds.setByteOrder(QDataStream::BigEndian);

  short len;
  ds >> len;
  
  if (len != datalen - 2) {
    qWarning("Server response incorrect.  %s", __FUNCTION__);
  } 
  
  Q_INT8  stat;
  ds >> stat;
  if (stat) {
    int ret = QMessageBox::critical(0, "mount dictionary error",
				    "mount dictionary error",
				    QMessageBox::Ok | QMessageBox::Default, 0);
    if (ret) qApp->quit();
  }  
  qDebug("mount dictionary successful.");

  mountDictionary();
}



void
SockKanjiClient::beginConvert(const QString& hira)
{
  if ( _convertflag ) {
    qWarning("%s:_convertflag incorrect", __FUNCTION__);
    return;
  }

  QByteArray  pack;
  QDataStream ds(pack, IO_WriteOnly);
  ds.setByteOrder(QDataStream::BigEndian);

  qDebug("Yomigana size(QCString): %d", hira.local8Bit().length());
  QByteArray str = eucToUshort(hira.local8Bit());
  qDebug("Yomigana size(ushort array): %d", str.size());

  ds << (uchar)0x0f << (uchar)0 << (ushort)(str.size()+6) << 0L << _contxt;
  ds.writeRawBytes(str.data(), str.size());
  
  //qDebug("packet size: %d", pack.size());
  //qDebug("dic: %d", dic.length());
  
  _sock->writeBlock(pack.data(), pack.size());

  _convertflag = TRUE;  // start converting
  qDebug("beginConvert in progress ...");
}


void 
SockKanjiClient::recvBeginConvertReply()
{
  uint datalen = _sock->size();
  QByteArray data(datalen);
  if (_sock->readBlock(data.data(), datalen) < 0) {
    qFatal("Recv error.  %s", __FUNCTION__);
  }

  QDataStream ds(data, IO_ReadOnly);
  ds.setByteOrder(QDataStream::BigEndian);

  short len;
  ds >> len;
  
  if (len != datalen - 2) {
    qWarning("beginConvert: server response incorrect. (%s)", __FUNCTION__);
  } 

  short nbunsetu;
  ds >> _nbunsetu;
  if (_nbunsetu <= 0) {
    qDebug("beginConvert: convert error.  %s", __FUNCTION__);
    return;
  }
  //qDebug("Number of Bunsetu: %d", _nbunsetu);

  QStringList  listkanji;
  for (int i = 0; i < _nbunsetu; i++) {
    QByteArray  ba;
    QDataStream dsba(ba, IO_WriteOnly);
    dsba.setByteOrder(QDataStream::BigEndian);
    ushort us;

    ds >> us;
    while (us) {
      dsba << us;
      ds >> us;
    }

    QString str = (tr(ushortToEuc(ba)));
    //qDebug("str :%s", str.local8Bit());
    listkanji.append(str);
  }

  qDebug("beginConvert successful");
  emit allPhasesConvt(listkanji);
  
}


void 
SockKanjiClient::endConvert()
{
  if ( !_convertflag ) {
    qDebug("%s: unnecessary to end convert", __FUNCTION__);
    return;
  }

  QByteArray  pack;
  QDataStream ds(pack, IO_WriteOnly);
  ds.setByteOrder(QDataStream::BigEndian);

  ds << (uchar)0x10 << (uchar)0 << (ushort)8 << _contxt 
   //  << (short)_nbunsetu << 0L;
   << (short)0 << 0L;

  _sock->writeBlock(pack.data(), pack.size());

  _nbunsetu = 0;
  _convertflag = FALSE;  // conversion done
  qDebug("%s: done", __FUNCTION__);
}


void 
SockKanjiClient::recvEndConvertReply() const
{
  uint datalen = _sock->size();
  QByteArray data(datalen);
  if (_sock->readBlock(data.data(), datalen) < 0) {
    qFatal("Recv error.  %s", __FUNCTION__);
    return;
  }
  
  QDataStream ds(data, IO_ReadOnly);
  ds.setByteOrder(QDataStream::BigEndian);
  
  short len;
  ds >> len;
  
  if (len != datalen - 2) {
    qWarning("Server response incorrect.  %s", __FUNCTION__);
    return;
  }
  
  Q_INT8 res;
  ds >> res;
  if ( res ) {
    qFatal("End Convert error.  %s", __FUNCTION__);
    return;
  } 
    
  qDebug("endConvert successful");
}


void 
SockKanjiClient::getCandidacyList(short bun)
{
  if ( !_convertflag ) return;
 
  QByteArray  pack;
  QDataStream ds(pack, IO_WriteOnly);
  ds.setByteOrder(QDataStream::BigEndian);

  ds << (uchar)0x11 << (uchar)0 << (ushort)6 << _contxt 
     << bun << (ushort)4096;
  
  _sock->writeBlock(pack.data(), pack.size()); 
}


void 
SockKanjiClient::getYomi(short idx)
{
  if ( !_convertflag ) return;

  QByteArray  pack;
  QDataStream ds(pack, IO_WriteOnly);
  ds.setByteOrder(QDataStream::BigEndian);
  
  ds << (uchar)0x12 << (uchar)0 << (ushort)6 << _contxt 
     << idx << (ushort)4096;
  
  _sock->writeBlock(pack.data(), pack.size());
}


void 
SockKanjiClient::recvGetYomiReply()
{
  uint datalen = _sock->size();
  QByteArray data(datalen);
  if (_sock->readBlock(data.data(), datalen) < 0) {
    qFatal("Recv error.  %s", __FUNCTION__);
    return;
  }
  
  QDataStream ds(data, IO_ReadOnly);
  ds.setByteOrder(QDataStream::BigEndian);
  
  short len;
  ds >> len;
  
  if (len != datalen - 2) {
    qWarning("Server response incorrect.  %s", __FUNCTION__);
    return;
  }
  
  short n;
  ds >> n;
  
  QByteArray  ba;
  QDataStream dsba(ba, IO_WriteOnly);
  dsba.setByteOrder(QDataStream::BigEndian);
  ushort us;
  
  ds >> us;
  while (us) {
    dsba << us;
    ds >> us;
  }    
  
  QString str = (tr(ushortToEuc(ba)));
  emit currentYomi(str);
}


void 
SockKanjiClient::recvGetCandidacyListReply()
{
  uint datalen = _sock->size();
  QByteArray data(datalen);
  if (_sock->readBlock(data.data(), datalen) < 0) {
    qFatal("Recv error.  %s", __FUNCTION__);
    return;
  }
  
  QDataStream ds(data, IO_ReadOnly);
  ds.setByteOrder(QDataStream::BigEndian);
  
  short len;
  ds >> len;
  
  if (len != datalen - 2) {
    qWarning("Server response incorrect.  %s", __FUNCTION__);
    return;
  }

  short ncand;
  ds >> ncand;
  
  if (ncand < 0) {
    qFatal("GetCandidacyList error.  %s", __FUNCTION__);
    return;
  }
  qDebug("Number of Candidates: %d", ncand);
  
  QStringList  listcand;

  for (int i = 0; i < ncand; i++) {
    QByteArray  ba;
    QDataStream dsba(ba, IO_WriteOnly);
    dsba.setByteOrder(QDataStream::BigEndian);
    ushort us;
    
    ds >> us;
    while (us) {
      dsba << us;
      ds >> us;
    }    
    
    QString str = (tr(ushortToEuc(ba)));
    //qDebug("str :%s", str.local8Bit());
    listcand.append(str);
  }

  emit candidacyList(listcand);
}


void
SockKanjiClient::resizePause(short idx, short len)
{
  QByteArray  pack;
  QDataStream ds(pack, IO_WriteOnly);
  ds.setByteOrder(QDataStream::BigEndian);
  
  ds << (uchar)0x1a << (uchar)0 << (ushort)6 << _contxt 
     << idx << (ushort)len;
  
  _sock->writeBlock(pack.data(), pack.size());  
}


void 
SockKanjiClient::recvResizePause()
{
  uint datalen = _sock->size();
  QByteArray data(datalen);
  if (_sock->readBlock(data.data(), datalen) < 0) {
    qFatal("Recv error.  %s", __FUNCTION__);
    return;
  }
  
  QDataStream ds(data, IO_ReadOnly);
  ds.setByteOrder(QDataStream::BigEndian);
  
  short len;
  ds >> len;
  
  if (len != datalen - 2) {
    qWarning("Server response incorrect.  %s", __FUNCTION__);
    return;
  }
  
  short nbunsetu;
  ds >> _nbunsetu;
  if (_nbunsetu <= 0) {
    qDebug("Convert error.  %s", __FUNCTION__);
    return;
  }
  
  qDebug("%s Number of Bunsetu: %d", __FUNCTION__, _nbunsetu);

  QStringList  listkanji;

  for (int i = 0; i < _nbunsetu; i++) {
    QByteArray  ba;
    QDataStream dsba(ba, IO_WriteOnly);
    dsba.setByteOrder(QDataStream::BigEndian);
    ushort us;

    ds >> us;
    while (us && !ds.atEnd()) {
      dsba << us;
      ds >> us;
    }
    
    QString str = (tr(ushortToEuc(ba)));
    //qDebug("str :%s", str.local8Bit());
    if ( !str.isEmpty() ) listkanji.append(str);
  }

  //QCString str = ushortToEuc(bufstr);
  //qDebug("str length: %d", str.length());
  emit reconvtPhases(listkanji);
}


// 返却値はネットワークバイトオーダ変換済なので，直ちに送信可能
// 末尾に (ushort)0 が追加
QByteArray 
SockKanjiClient::eucToUshort(const QCString& src)
{
  QByteArray dest;
  QDataStream dsdest(dest, IO_ReadWrite);
  dsdest.setByteOrder(QDataStream::BigEndian);

  QCString::ConstIterator it = src.begin();
  while (it != src.end()) {

    if (!(*it & 0x80)) {
      // ASCII
      dsdest << (ushort)*it;
    } else if (it+1 != src.end()) {
      switch ((uchar)*it) {
      case 0x8e:   // 半角カナ	
	dsdest << (ushort)(0x0080 | (*(++it) & 0x7f));
	break;
      case 0x8f:   // 外字
	if (it+2 == src.end()) return 0;
	dsdest << (ushort)(0x8000 | ((*(++it) & 0x7f) << 8) | (*(++it) & 0x7f));
	break;
      default:  // 全角かな
	dsdest << (ushort)(0x8080 | ((*it & 0x7f) << 8) | (*(++it) & 0x7f));
	break;
      }
    } else {
      // error
      return 0;
    }

    it++;
  }
  
  return dest;
}


// 引数はネットワークバイトオーダの配列を指定
QCString 
SockKanjiClient::ushortToEuc(const QByteArray& src)
{
  QDataStream dssrc(src, IO_ReadOnly);
  dssrc.setByteOrder(QDataStream::BigEndian);
  QCString dest;

  while (!dssrc.atEnd()) {
    ushort us;
    dssrc >> us;
 
    switch (us & 0x8080) {
    case 0:  /* ASCII */
      dest += (uchar)(us & 0x007f);
      break;

    case 0x0080:  /* 半角カナ */
      dest += (uchar)0x8e;
      dest += (uchar)((us & 0x007f) | 0x80);
      break;

    case 0x8000:  /* 外字 */
      dest += (uchar)0x8f;
      dest += (uchar)(((us & 0x7f00) >> 8) | 0x80);
      dest += (uchar)((us & 0x007f) | 0x80);
      break;

    case 0x8080:  /* 全角かな */
      dest += (uchar)(((us & 0x7f00) >> 8) | 0x80);
      dest += (uchar)((us & 0x007f) | 0x80);
      break;

    default:
      Q_ASSERT(0);
      break;
    }
  }
  
  return dest;
}



