#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <time.h>
#include <sys/stat.h>
#include <errno.h>
#include <wslib.h>

#include "connap.h"
#include "message.h"
#include "connect.h"
#include "search.h"
#include "transfer.h"
#include "shared.h"
#include "util.h"


DOWNLOAD *downloads;
UPLOAD *uploads;

int dest_ip, dest_port;

int data_sock = 0;

static int fw_addr = -1, fw_fd;

static char *cur_dl_dir = NULL;

static void download2(DOWNLOAD *new_dl, int offset);


#if 0

static String MapUlName(String name)
{
   static String mappedName = NULL;
   String p;

   if (mappedName)
       XtFree(mappedName);

   mappedName = XtNewString(name);
   for (p = (mappedName + 2); *p; p++) {
       if ((*p) == '\\')
           (*p) = '/';
   }
   return mappedName + 2;
}


static void UpdateDlWin(XtPointer clientData, XtInputId *id)
{
   DOWNLOAD *rec = (DOWNLOAD*)clientData;
   int percent, sec;
   char tmp[128];

   percent = (rec->count * 100) / rec->tot;

   XmScaleSetValue(XtNameToWidget(rec->w, "*dlProgBar"), percent);

   sec = time(NULL) - rec->start;
   sprintf(tmp, "%d%%   %.2f kb/sec", percent,
           sec ? ((float)(rec->count - rec->offset) / 1024.0) /
           (float)sec : 0.00);
   XmTextFieldSetString(XtNameToWidget(rec->w, "*dlStatusLabel"), tmp);

   rec->timerId = XtAppAddTimeOut(appCon, 1000,
           (XtTimerCallbackProc)UpdateDlWin, clientData);
}


static void ReadMP3Data(XtPointer clientData, int *sock, XtInputId *id)
{
   DOWNLOAD *rec = (DOWNLOAD*)clientData;
   DOWNLOAD *newDl;
   int n, r;
   String msg;

   n = read(*sock, Buf, sizeof(Buf));
   if (n <= 0) {
       newDl = XtNew(DOWNLOAD);
       newDl->fileName = XtNewString(rec->fileName);
       newDl->dlName = XtNewString(rec->dlName);
       newDl->nick = XtNewString(rec->nick);
       newDl->tot = rec->tot;
       newDl->count = rec->count;
       newDl->w = NULL;
       newDl->inputId = newDl->fd = newDl->mp3Fd = 0;

       EndDl(rec, 1, 0);

       if (newDl->count < newDl->tot) {
           msg = XtMalloc(1024);
           sprintf(msg, "%s\n\nPremature EOF. Resume?",
                   newDl->dlName);
           r = YesNoMsg(msg, "OK");
           XtFree(msg);
           if (r) {
               Download2(newDl, newDl->count);
           } else
               remove(newDl->dlName);
       } else {
           XtFree(newDl->fileName);
           XtFree(newDl->dlName);
           XtFree(newDl->nick);
           XtFree((char*)newDl);
       }
       return;
   }

   if (write(rec->mp3Fd, Buf, n) == -1)
       AbortDl(rec, 1, strerror(errno));

   rec->count += n;
}


static void WriteMP3Data(XtPointer clientData, int *sock, XtInputId *id)
{
   int n, err, optLen = sizeof(int);
   UPLOAD *rec = (UPLOAD*)clientData;

   if((n = read(rec->mp3Fd, Buf, 1024)) <= 0) {
       EndUl(rec, 1);
       return;
   }

   if (WriteChars(*sock, Buf, n) == -1) {
       EndUl(rec, 1);
       return;
   }

   if (getsockopt(*sock, SOL_SOCKET, SO_ERROR,
           (char*)&err, &optLen) == -1) {
       EndUl(rec, 1);
       return;
   }
   if (err != 0) {
       EndUl(rec, 1);
       return;
   }

   rec->count += n;
}


void EndDl(DOWNLOAD *rec, int dlStat, int aborted) {
   if (rec->inputId)
       XtRemoveInput(rec->inputId);

   if (rec->fd > 0)
       close(rec->fd);
   if (rec->mp3Fd > 0)
       close(rec->mp3Fd);

   if (aborted && rec->dlName) {
       remove(rec->dlName);
       XtFree(rec->dlName);
   }

   if (dlStat) {
       if (rec->w) {
           XtRemoveTimeOut(rec->timerId);
           XtDestroyWidget(rec->w);
           curFocus = NULL;
       }
       if (SendMsg(MSG_CLIENT_DOWNLOAD_END, ""))
           Disconnect(strerror(errno));
   }

   XtFree(rec->nick);
   XtFree(rec->fileName);
   XtFree((char*)rec);
}


void AbortDl(DOWNLOAD *rec, int dlStat, String err) {
   SimpleMsgRemove();
   EndDl(rec, dlStat, 1);
   if (strlen(err))
       ErrMsg(err);
}


static void Unreachable(String nick)
{
   if (SendMsg(MSG_CLIENT_DATA_PORT_ERROR, nick))
       Disconnect(strerror(errno));
}


static void Download2(DOWNLOAD *newDl, int offset)
{
   String tmp = XtMalloc(8192);
   int i, r, fwDl, openFlags;
   char byte, offsetStr[20];
   struct in_addr sin_addr;

   sprintf(offsetStr, "%d", offset);

   SimpleMsg("Preparing for download...");

   destIp = destPort = -1;

   sprintf(tmp, "%s \"%s\"", newDl->nick, newDl->fileName);

   if (SendMsg(MSG_CLIENT_DOWNLOAD, tmp)) {
       Disconnect(strerror(errno));
       AbortDl(newDl, 0, strerror(errno));
       goto error;
   }

   WaitVar(&destPort, timeOut);
   if (destPort == -1) {
       AbortDl(newDl, 0, "Timeout");
       goto error;
   }

   fwDl = 0;

   if (destPort == 0) {
       /* Firewalled download */
       fwDl = 1;

       if (SendMsg(MSG_CLIENT_DOWNLOAD_FIREWALL, tmp)) {
           AbortDl(newDl, 0, strerror(errno));
           Disconnect(strerror(errno));
           goto error;
       }

       fwAddr = destIp;
       fwFd = -1;
       WaitVar(&fwFd, timeOut);
       if (fwFd == -1) {
           AbortDl(newDl, 0, "Timeout");
           goto error;
       }
       newDl->fd = fwFd;

       if ((r = WriteChars(newDl->fd, "1", 1)) == -1) {
           AbortDl(newDl, 0, strerror(errno));
           goto error;
       }

       tmp[4] = 0;
       if ((r = ReadChars(newDl->fd, tmp, 4)) < 4) {
           AbortDl(newDl, 0, ReadErr(r));
           goto error;
       }

       if (strcmp(tmp, "SEND")) {
           AbortDl(newDl, 0, "EOF from peer or invalid data");
           goto error;
       }

       memset(tmp, 0, 8192);
       do {
           r = read(newDl->fd, tmp, 8192);
       } while ((r == -1) && (errno == EAGAIN));
       if (r <= 0) {
           AbortDl(newDl, 0, "EOF from peer of invalid data");
           goto error;
       }

       if ((r = WriteChars(newDl->fd, offsetStr,
               strlen(offsetStr))) == -1) {
           AbortDl(newDl, 0, strerror(errno));
           goto error;
       }
   } else {
       sin_addr.s_addr = destIp;

       if (! ConnSock(inet_ntoa(sin_addr), destPort, &newDl->fd)) {
           Unreachable(newDl->nick);
           AbortDl(newDl, 0, "");
           goto error;
       }

       tmp[1] = 0;
       if ((r = ReadChars(newDl->fd, tmp, 1)) < 1) {
           AbortDl(newDl, 0, ReadErr(r));
           goto error;
       }

       if (strcmp(tmp, "1")) {
           AbortDl(newDl, 0, "EOF from peer or invalid data");
           goto error;
       }

       if ((r = WriteChars(newDl->fd, "GET", 3)) == -1) {
           AbortDl(newDl, 0, strerror(errno));
           goto error;
       }

       sprintf(tmp, "%s \"%s\" %s", userInfo.userName,
               newDl->fileName, offsetStr);

       if ((r = WriteChars(newDl->fd, tmp, strlen(tmp))) == -1) {
           AbortDl(newDl, 0, strerror(errno));
           goto error;
       }

       memset(tmp, 0, 8192);
       i = 0;
       while (1) {
           if ((r = ReadChars(newDl->fd, &byte, 1)) < 1) {
               AbortDl(newDl, 0, ReadErr(r));
               goto error;
           }
           if (! isdigit(byte))
               break;
           tmp[i++] = byte;
       }

       if (strtoul(tmp, NULL, 10) != newDl->tot) {
           AbortDl(newDl, 0,
                   "EOF from peer, file not shared or invalid data");
           goto error;
       }
   }

   if (SendMsg(MSG_CLIENT_DOWNLOAD_START, "")) {
       AbortDl(newDl, 0, strerror(errno));
       Disconnect("");
       goto error;
   }

   openFlags = O_CREAT | O_WRONLY;
   if (! offset)
       openFlags |= O_TRUNC;
   newDl->mp3Fd = open(newDl->dlName, openFlags);

   if (newDl->mp3Fd == -1) {
       AbortDl(newDl, 1, strerror(errno));
       goto error;
   }
   fchmod(newDl->mp3Fd, 0644);
   lseek(newDl->mp3Fd, offset, SEEK_SET);

   if (! fwDl) {
       if (write(newDl->mp3Fd, &byte, 1) == -1) {
           AbortDl(newDl, 1, strerror(errno));
           goto error;
       }
   }

   SimpleMsgRemove();

   newDl->w = DlWin(newDl);
   newDl->count = fwDl ? offset : offset + 1;
   newDl->offset = offset;
   newDl->start = time(NULL);

   ForceWindow(newDl->w);

   newDl->inputId = XtAppAddInput(appCon, newDl->fd,
           (XtPointer)XtInputReadMask,
           (XtInputCallbackProc)ReadMP3Data,
           (XtPointer)newDl);

   newDl->timerId = XtAppAddTimeOut(appCon, 100,
           (XtTimerCallbackProc)UpdateDlWin, (XtPointer)newDl);

error:
   XtFree(tmp);
}


void Download(int n, int mode)
{
   SRESULT *res;
   DOWNLOAD *newDl;
   String tmp = XtMalloc(8192), p, tail, dflt;
   int i, nr, offset;
   struct stat statBuf;

   for (nr = 0, res = resData; res; nr++, res = res->next);
   if (n > (nr - 1))
       goto error;
   for (i = 0, res = resData; i < n; i++)
       res = res->next;

   newDl = XtNew(DOWNLOAD);
   newDl->inputId = newDl->fd = newDl->mp3Fd = 0;
   newDl->dlName = NULL;
   newDl->w = NULL;

   strcpy(tmp, res->data);

   if (mode) {
       p = strtok(tmp, " ");
       newDl->nick = XtNewString(p);
       p = strtok(NULL, "\"");
       newDl->fileName = XtNewString(p);
       /* skip over md5 */
       (void)strtok(NULL, " ");
       p = strtok(NULL, " ");
       newDl->tot = atoi(p);
   } else {
       p = strtok(tmp, "\"");
       newDl->fileName = XtNewString(p);
       /* skip over md5 */
       (void)strtok(NULL, " ");
       p = strtok(NULL, " ");
       newDl->tot = atoi(p);
       /* skip over bitrate, frequency and length */
       (void)strtok(NULL, " ");
       (void)strtok(NULL, " ");
       (void)strtok(NULL, " ");
       p = strtok(NULL, " ");
       newDl->nick = XtNewString(p);
   }

   if (newDl->tot == 0) {
       AbortDl(newDl, 0, "file size is 0 bytes");
       goto error;
   }

   if (! curDlDir)
       curDlDir = XtNewString(initDlDir);
   if (! (dflt = strrchr(newDl->fileName, '\\') + 1))
       dflt = strrchr(newDl->fileName, '/') + 1;
   p = SaveFile(curDlDir, dflt);
   if (! *p) {
       AbortDl(newDl, 0, "");
       goto error;
   }

   XtFree(curDlDir);
   curDlDir = GetDir(p);

   tail = strrchr(p, '/') + 1;
   offset = 0;
   if (! strcmp(tail, dflt)) {
       if (stat(p, &statBuf) != -1) {
           if (statBuf.st_size == newDl->tot) {
               InfoMsg("File is complete");
               AbortDl(newDl, 0, "");
               goto error;
           }
           offset = statBuf.st_size;
       }
   }

   newDl->dlName = XtNewString(p);
   Download2(newDl, offset);

error:
   XtFree(tmp);
}


void EndUl(UPLOAD *rec, int ulStat)
{
   UPLOAD *upload, *prevUpload = NULL;

   if (! uploads)
       return;

   if (rec->inputId)
       XtRemoveInput(rec->inputId);

   close(rec->fd);
   if (rec->mp3Fd > 0)
       close(rec->mp3Fd);

   for (upload = uploads; upload; upload = upload->next) {
       if ((! strcmp(upload->nick, rec->nick)) &&
               (! strcmp(upload->fileName, rec->fileName)))
           break;
       prevUpload = upload;
   }
   if (! upload) {
       ErrMsg("Upload not in list");
       return;
   }

   if (prevUpload) {
       prevUpload->next = upload->next;
   } else {
       uploads = uploads->next;
   }

   XtFree(upload->nick);
   XtFree(upload->fileName);
   XtFree((char*)upload);

   if (ulStat) {
       if (SendMsg(MSG_CLIENT_UPLOAD_END, ""))
           Disconnect(strerror(errno));
   }
}


static int AddUl(UPLOAD *newUl)
{
   UPLOAD *ulPtr, *prevPtr = NULL;
   String newFileName, fileName;

   for (ulPtr = uploads; ulPtr; ulPtr = ulPtr->next) {
       if ((! strcmp(ulPtr->fileName, newUl->fileName)) &&
               (! strcmp(ulPtr->nick, newUl->nick)))
           return 0;
   }

   newFileName = GetFileTail(newUl->fileName);

   for (ulPtr = uploads; ulPtr; ulPtr = ulPtr->next) {
       fileName = GetFileTail(ulPtr->fileName);
       if (strcasecmp(fileName, newFileName) > 0) {
           XtFree(fileName);
           break;
       }
       XtFree(fileName);
       prevPtr = ulPtr;
   }
   if (prevPtr) {
       newUl->next = prevPtr->next;
       prevPtr->next = newUl;
   } else {
       newUl->next = uploads;
       uploads = newUl;
   }
   XtFree(newFileName);
   return 1;
}


void Upload(int fd)
{
   UPLOAD *newUl;
   SHARED *shared;
   String realName;
   String tmp = XtMalloc(8192);
   String nick, fileName, offset;
   int r;

   if ((r = WriteChars(fd, "1", 1)) == -1) {
       close(fd);
       goto error;
   }

   tmp[3] = 0;
   if ((r = ReadChars(fd, tmp, 3)) < 3) {
       close(fd);
       goto error;
   }
   if (strcmp(tmp, "GET")) {
       close(fd);
       goto error;
   }

   /* Get nick, filename and offset */
   memset(tmp, 0, 8192);
   do {
       r = read(fd, tmp, 8192);
   } while ((r == -1) && (errno == EAGAIN));
   if (r <= 0) {
       close(fd);
       goto error;
   }
   nick = strtok(tmp, " ");
   fileName = strtok(NULL, "\"");
   offset = strtok(NULL, " ");

   newUl = XtNew(UPLOAD);
   newUl->nick = XtNewString(nick);
   realName = MapUlName(fileName);
   newUl->fileName = XtNewString(realName);
   newUl->fd = fd;
   newUl->mp3Fd = 0;
   newUl->count = strtoul(offset, NULL, 10);
   newUl->inputId = 0;

   if (! AddUl(newUl)) {
       XtFree(newUl->nick);
       XtFree(newUl->fileName);
       XtFree((char*)newUl);
       close(newUl->fd);
       goto error;
   }

   for (shared = sharedFiles; shared; shared = shared->next) {
       if (! strcmp(shared->fileName, newUl->fileName))
           break;
   }
   if ((! shared) || (atoi(offset) > shared->size)) {
       WriteChars(newUl->fd, "FILE NOT SHARED",
               sizeof("FILE NOT SHARED"));
       EndUl(newUl, 0);
       goto error;
   }

   newUl->tot = shared->size;

   newUl->mp3Fd = open(newUl->fileName, O_RDONLY);
   if (newUl->mp3Fd == -1) {
       EndUl(newUl, 0);
       goto error;
   }
   lseek(newUl->mp3Fd, newUl->count, SEEK_SET);

   sprintf(tmp, "%d", shared->size);
   if ((r = WriteChars(fd, tmp, strlen(tmp))) == -1) {
       EndUl(newUl, 0);
       goto error;
   }

   if ((r = SendMsg(MSG_CLIENT_UPLOAD_START, ""))) {
       EndUl(newUl, 0);
       Disconnect(strerror(errno));
       goto error;
   }

   newUl->inputId = XtAppAddInput(appCon, newUl->fd,
           (XtPointer)XtInputWriteMask,
           (XtInputCallbackProc)WriteMP3Data,
           (XtPointer)newUl);

error:
   XtFree(tmp);
}


void FwUpload(String data)
{
   String nick, fileName, realName, md5;
   unsigned long ip;
   int fd, port, link, offset;
   struct in_addr sin_addr;
   String tmp = XtMalloc(8192), p;
   UPLOAD *newUl;
   SHARED *shared;
   int r;

   nick = strtok(data, " ");
   ip = atol(strtok(NULL, " "));
   port = atoi(strtok(NULL, " "));
   fileName = strtok(NULL, "\"");
   md5 = strtok(NULL, " ");
   link = atoi(strtok(NULL, " "));

   sin_addr.s_addr = destIp;
   if (! ConnSock(inet_ntoa(sin_addr), port, &fd)) {
       Unreachable(nick);
       goto error;
   }

   newUl = XtNew(UPLOAD);
   newUl->nick = XtNewString(nick);
   realName = MapUlName(fileName);
   newUl->fileName = XtNewString(realName);
   newUl->fd = fd;
   newUl->mp3Fd = 0;
   newUl->inputId = 0;

   if (! AddUl(newUl)) {
       XtFree(newUl->nick);
       XtFree(newUl->fileName);
       XtFree((char*)newUl);
       close(newUl->fd);
       goto error;
   }

   for (shared = sharedFiles; shared; shared = shared->next) {
       if (! strcmp(shared->fileName, newUl->fileName))
           break;
   }
   if (! shared) {
       EndUl(newUl, 0);
       goto error;
   }

   newUl->tot = shared->size;

   newUl->mp3Fd = open(newUl->fileName, O_RDONLY);
   if (newUl->mp3Fd == -1) {
       EndUl(newUl, 0);
       goto error;
   }

   tmp[1] = 0;
   if ((r = ReadChars(newUl->fd, tmp, 1)) < 1) {
       EndUl(newUl, 0);
       goto error;
   }

   if (strcmp(tmp, "1")) {
       EndUl(newUl, 0);
       goto error;
   }

   if ((r = WriteChars(newUl->fd, "SEND", 4)) == -1) {
       EndUl(newUl, 0);
       goto error;
   }

   sprintf(tmp, "%s \"%s\" %u", userInfo.userName, fileName,
       shared->size);

   if ((r = WriteChars(newUl->fd, tmp, strlen(tmp))) == -1) {
       EndUl(newUl, 0);
       goto error;
   }

   /* Get offset */
   memset(tmp, 0, 8192);
   do {
       r = read(newUl->fd, tmp, 8192);
   } while ((r == -1) && (errno == EAGAIN));
   if (r <= 0) {
       EndUl(newUl, 0);
       goto error;
   }
   for (p = tmp; *p; p++) {
       if (! isdigit(*p)) {
           EndUl(newUl, 0);
           goto error;
       }
   }

   if ((offset = atoi(tmp)) > shared->size) {
       EndUl(newUl, 0);
       goto error;
   }
   newUl->count = offset;
   lseek(newUl->mp3Fd, newUl->count, SEEK_SET);

   if ((r = SendMsg(MSG_CLIENT_UPLOAD_START, ""))) {
       EndUl(newUl, 0);
       Disconnect(strerror(errno));
       goto error;
   }

   newUl->inputId = XtAppAddInput(appCon, newUl->fd,
           (XtPointer)XtInputWriteMask,
           (XtInputCallbackProc)WriteMP3Data,
           (XtPointer)newUl);
error:
   XtFree(tmp);
}


void AcceptConn(XtPointer closure, int *sock, XtInputId *id)
{
   int new, size;
   struct sockaddr_in clientName;

   size = sizeof(clientName);
   new = accept(*sock, (struct sockaddr*)&clientName, &size);
   if (new < 0)
       return;

   if (SetBlocked(new, 0)) {
       close(new);
       return;
   }

/*
   printf("Connect from host %s, port %hd\n",
           inet_ntoa(clientName.sin_addr),
           ntohs(clientName.sin_port));
*/

   if ((clientName.sin_addr.s_addr == fwAddr) ||
           ((ntohl(clientName.sin_addr.s_addr) == 0x7f000001) &&
               (fwAddr != -1))) {
       fwFd = new;
       fwAddr = -1;
       return;
   }

   Upload(new);
   return;
}


static int InitDataPort(int port, int manual)
{
   char tmp[256];

   if (MakeSocket(port, &dataSock) < 0) {
       if (manual)
           ErrMsg(strerror(errno));
       return 0;
   }
   if (listen(dataSock, 1) < 0) {
       close(dataSock);
       if (manual) {
           sprintf(tmp, "Listening on data port %d failed.\n"
                   "You are probably running a previous\n"
                   "instance of XmNap with the same port\n"
                   "number as this one", port);
           ErrMsg(tmp);
       }
       return 0;
   }
   return 1;
}


void SetDataPort(int port, int manual)
{
   char tmp[256];

   if (dataSockId) {
       XtRemoveInput(dataSockId);
       close(dataSock);
       dataSockId = 0;
   }

   if (port != 0) {
       if (! InitDataPort(port, manual)) {
           userInfo.dataPort = 0;
           if (srvConn) {
               if (SendMsg(MSG_CLIENT_CHANGE_DATA_PORT, "0"))
                   Disconnect(strerror(errno));
           }
           return;
       }
       dataSockId = XtAppAddInput(appCon, dataSock,
               (XtPointer)XtInputReadMask,
               (XtInputCallbackProc)AcceptConn, NULL);
   }

   userInfo.dataPort = port;

   if (manual && srvConn) {
       sprintf(tmp, "%d", userInfo.dataPort);
       if (SendMsg(MSG_CLIENT_CHANGE_DATA_PORT, tmp))
               Disconnect(strerror(errno));
   }
}

#endif