#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>

#include "QMIThread.h"

#ifdef CONFIG_GOBINET

// IOCTL to generate a client ID for this service type
#define IOCTL_QMI_GET_SERVICE_FILE 0x8BE0 + 1

// IOCTL to get the VIDPID of the device
#define IOCTL_QMI_GET_DEVICE_VIDPID 0x8BE0 + 2

// IOCTL to get the MEID of the device
#define IOCTL_QMI_GET_DEVICE_MEID 0x8BE0 + 3

static int GobiNetSendQMI(PQCQMIMSG pRequest)
{
    int ret, fd;

    static int send_count = 0;
    fd = qmiclientId[pRequest->QMIHdr.QMIType];

    if (fd <= 0) {
	dbg_time("%s QMIType: %d has no clientID", __func__,
		 pRequest->QMIHdr.QMIType);
	return -ENODEV;
    }

    // Always ready to write
re_write:
    if (1 == 1) {
        ssize_t nwrites =
            le16_to_cpu(pRequest->QMIHdr.Length) + 1 - sizeof(QCQMI_HDR);
        ret = write(fd, &pRequest->MUXMsg, nwrites);
        if (ret == nwrites)
        {
            ret = 0;
            send_count = 0;
        }
        else
        {
            send_count++;
            dbg_time("%s write=%d, errno: %d (%s) send_count %d", __func__, ret, errno, strerror(errno), send_count);
            if (send_count < 3)
            {
                sleep(1);
                goto re_write;
            }
        }

    } else {
	dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno,
		 strerror(errno));
    }

    return ret;
}

static int GobiNetGetClientID(const char *qcqmi, UCHAR QMIType)
{
    int ClientId;
    ClientId = open(qcqmi, O_RDWR | O_NONBLOCK | O_NOCTTY);
    if (ClientId == -1) {
	dbg_time("failed to open %s, errno: %d (%s)", qcqmi, errno,
		 strerror(errno));
	return -1;
    }

    if (ioctl(ClientId, IOCTL_QMI_GET_SERVICE_FILE, QMIType) != 0) {
	dbg_time("failed to get ClientID for 0x%02x errno: %d (%s)", QMIType,
		 errno, strerror(errno));
	close(ClientId);
	ClientId = 0;
    }

    dbg_time("%s: QMIType = %d clientid %d", __func__, QMIType, ClientId);

    switch (QMIType) {
    case QMUX_TYPE_WDS:
	dbg_time("Get clientWDS = %d", ClientId);
	break;
    case QMUX_TYPE_DMS:
	dbg_time("Get clientDMS = %d", ClientId);
	break;
    case QMUX_TYPE_NAS:
	dbg_time("Get clientNAS = %d", ClientId);
	break;
    case QMUX_TYPE_QOS:
	dbg_time("Get clientQOS = %d", ClientId);
	break;
    case QMUX_TYPE_WMS:
	dbg_time("Get clientWMS = %d", ClientId);
	break;
    case QMUX_TYPE_PDS:
	dbg_time("Get clientPDS = %d", ClientId);
	break;
    case QMUX_TYPE_UIM:
	dbg_time("Get clientUIM = %d", ClientId);
	break;
    case QMUX_TYPE_WDS_ADMIN:
	dbg_time("Get clientWDA = %d", ClientId);
	break;
    default:
	break;
    }

    return ClientId;
}

static int GobiNetDeInit(void)
{
    unsigned int i;
    for (i = 0; i < sizeof(qmiclientId) / sizeof(qmiclientId[0]); i++) {
	if (qmiclientId[i] != 0) {
	    close(qmiclientId[i]);
	    qmiclientId[i] = 0;
	}
    }

    return 0;
}
static void *GobiNetThread(void *pData)
{
    PROFILE_T *profile = (PROFILE_T *)pData;
    const char *qcqmi = (const char *)profile->qmichannel;
    int wait_for_request_quit = 0;
    dbg_time("%s %d", __func__, __LINE__);
    if (profile->ipv4_flag)
	    qmiclientId[QMUX_TYPE_WDS] = GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS);
    if (profile->ipv6_flag)
	qmiclientId[QMUX_TYPE_WDS_IPV6] = GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS);
    qmiclientId[QMUX_TYPE_DMS] = GobiNetGetClientID(qcqmi, QMUX_TYPE_DMS);
    qmiclientId[QMUX_TYPE_NAS] = GobiNetGetClientID(qcqmi, QMUX_TYPE_NAS);
    qmiclientId[QMUX_TYPE_UIM] = GobiNetGetClientID(qcqmi, QMUX_TYPE_UIM);
    // qmiclientId[QMUX_TYPE_WDS_ADMIN] =
//	GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS_ADMIN);

    //if ((qmiclientId[QMUX_TYPE_WDS] == 0) && (qmiclientId[QMUX_TYPE_WDS_IPV6] == 0)) /*|| (clientWDA == -1)*/ {

    if ((qmiclientId[QMUX_TYPE_DMS] == 0) ||
        (qmiclientId[QMUX_TYPE_NAS] == 0) ||
        (qmiclientId[QMUX_TYPE_UIM] == 0) ||
        (profile->ipv4_flag ? ((qmiclientId[QMUX_TYPE_WDS] == 0) ? 1 : 0):0)||
        (profile->ipv6_flag ? ((qmiclientId[QMUX_TYPE_WDS_IPV6] == 0) ? 1 : 0):0))
    {
        GobiNetDeInit();
        dbg_time("%s Failed to open %s, errno: %d (%s)", __func__, qcqmi, errno,strerror(errno));
        qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED);
        pthread_exit(NULL);
        return NULL;
    }

    qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED);

    while (1) {
	struct pollfd pollfds[16] = {{qmidevice_control_fd[1], POLLIN, 0}};
	int ne, ret, nevents = 1;
	unsigned int i;
	for (i = 0; i < sizeof(qmiclientId) / sizeof(qmiclientId[0]); i++) {
	    if (qmiclientId[i] != 0) {
		pollfds[nevents].fd = qmiclientId[i];
		pollfds[nevents].events = POLLIN;
		pollfds[nevents].revents = 0;
		nevents++;
	    }
	}

	do {
	    ret = poll(pollfds, nevents, wait_for_request_quit ? 1000 : -1);
	} while ((ret < 0) && (errno == EINTR));

	if (ret == 0 && wait_for_request_quit) {
	    QmiThreadRecvQMI(
		NULL);	// main thread may pending on QmiThreadSendQMI()
	    continue;
	}

	if (ret <= 0) {
	    dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno,
		     strerror(errno));
	    break;
	}

	for (ne = 0; ne < nevents; ne++) {
	    int fd = pollfds[ne].fd;
	    short revents = pollfds[ne].revents;

	    if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
			dbg_time("%s poll err/hup/inval", __func__);
			dbg_time("epoll fd = %d, events = 0x%04x", fd, revents);
			if (fd == qmidevice_control_fd[1]) {
			} else {
			}
			if (revents & (POLLERR | POLLHUP | POLLNVAL))
				goto __GobiNetThread_quit;
		}
	    if ((revents & POLLIN) == 0)
		continue;

	    if (fd == qmidevice_control_fd[1]) {
		int triger_event;
		if (read(fd, &triger_event, sizeof(triger_event)) ==
		    sizeof(triger_event)) {
		    // DBG("triger_event = 0x%x", triger_event);
		    switch (triger_event) {
		    case RIL_REQUEST_QUIT:
			goto __GobiNetThread_quit;
			break;
		    case SIGTERM:
			wait_for_request_quit = 1;
			break;
		    default:
			break;
		    }
		}
		continue;
	    }

	    {
		ssize_t nreads;
		static UCHAR QMIBuf[4096];
		PQCQMIMSG pResponse = (PQCQMIMSG)QMIBuf;

		nreads = read(fd, &pResponse->MUXMsg,
			      sizeof(QMIBuf) - sizeof(QCQMI_HDR));
		if (nreads <= 0) {
		    dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads,
			     errno, strerror(errno));
		    break;
		}

		for (i = 0; i < sizeof(qmiclientId) / sizeof(qmiclientId[0]);
		     i++) {
		    if (qmiclientId[i] == fd) {
			pResponse->QMIHdr.QMIType = i;
		    }
		}

		pResponse->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI;
		pResponse->QMIHdr.Length =
		    cpu_to_le16(nreads + sizeof(QCQMI_HDR) - 1);
		pResponse->QMIHdr.CtlFlags = 0x00;
		pResponse->QMIHdr.ClientId = fd & 0xFF;

		QmiThreadRecvQMI(pResponse);
	    }
	}
    }

__GobiNetThread_quit:
    GobiNetDeInit();
    qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED);
    QmiThreadRecvQMI(NULL);  // main thread may pending on QmiThreadSendQMI()
    dbg_time("%s exit", __func__);
    pthread_exit(NULL);
    return NULL;
}

#else
static int GobiNetSendQMI(PQCQMIMSG pRequest) { return -1; }
static void *GobiNetThread(void *pData)
{
    dbg_time("please set CONFIG_GOBINET");
    return NULL;
}
#endif

const struct qmi_device_ops gobi_qmidev_ops = {
    .deinit = GobiNetDeInit,
    .send = GobiNetSendQMI,
    .read = GobiNetThread,
};
