///
//     @file    mcastRelay.cxx
//     @date    Thu Aug  9 18:56:14 2018
// 
//     Copyright 2018 by ZeeVee, Inc.
//
#include "comp_defines.hxx"

#ifdef _WIN32
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <windows.h>
#include <iphlpapi.h>
#include <ipifcons.h>
#include <shlobj.h>
extern "C" {
   WINSOCK_API_LINKAGE  INT WSAAPI inet_pton( INT Family, PCSTR pszAddrString, PVOID pAddrBuf);
   WINSOCK_API_LINKAGE  PCSTR WSAAPI inet_ntop(INT  Family, PVOID pAddr, PSTR pStringBuf, size_t StringBufSize);
}
#else
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>       // IPPROTO_RAW, IPPROTO_IP, IPPROTO_UDP, INET_ADDRSTRLEN
#include <netinet/ip.h>       // struct ip and IP_MAXPACKET (which is 65535)
#include <stdio.h>
#include <stdarg.h>

#include <ifaddrs.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
//#include <linux/wireless.h>

#endif

#include <assert.h>
#include <sys/types.h>
#include <chrono>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <thread>
#include <mutex>
#include <fstream>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <deque>
#include <sstream>
//#include "stringtok.hxx"
#include <stdexcept>
#include <list>
#include "civetweb.h"
#include "mcastRelay.hxx"
#include "stringtok.hxx"


#define PORT "8154,8155"
#define DOCUMENT_ROOT "."

#define LOGGING 1
#ifndef LOGGING
#define LOG(args...) { printf(args); fflush(stdout); }
#endif


// 172.16.2.169:8154/hlsMcast.m3u8
// "c:\MinGW\msys\1.0\home\jgreene\hello.exe"  "%1"
// \HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\ProtocolExecute
//              WarnOnOpen DWORD => 0
// 
// https://zeevee.zoom.us/my/jchordas

// Computer\HKEY_CURRENT_USER\Software\MozillaPlugins\@zoom.us/ZoomVideoPlugin

using namespace std;
using namespace std::chrono;

McastRelay mcastRelay;

int getMyIp();
int check_wireless(const char* ifname, char* protocol) ;


int gdb()
{
    LOG("stupid windows\n");
    return 0;
}

int main(int argc, char *argv[])
{
    LOG("main\n");
#ifndef _WIN32
    pid_t pID = fork();
    if (pID == 0)  {              // child
        // Code only executed by child process    
        mcastRelay.Main();
    }
    else if (pID < 0) {
        cerr << "Failed to fork" << endl;
        return 1;
       // Throw exception
    }
    else                                   // parent
    {
      // Code only executed by parent process

        printf("Parent Process:\n");
    }       
#else
        mcastRelay.Main();
#endif

    return 0;
}

int McastRelay::Main()
{
    LOG("Starting...\n");
    mcastAddr = 0;
    hostAddrStr = "127.0.0.1";
    firstMcastPkt = true;
    firstTsRead = true;
       

    LOG("Main\n");
#ifdef _WIN32
	IP_ADAPTER_INFO* ipInfo = 0;
	ULONG ipInfoLen	= 0;
	DWORD err;
    
	err = GetAdaptersInfo(ipInfo, &ipInfoLen);
    if (err != ERROR_BUFFER_OVERFLOW)
        return 0;

    ipInfo = (IP_ADAPTER_INFO*) new char [ipInfoLen];
	err = GetAdaptersInfo(ipInfo, &ipInfoLen);

    while (ipInfo) {
        string descr = ipInfo->Description;
        if (descr.find("Bluetooth") == string::npos && descr.find("Virtual") == string::npos && descr.find("Loopback") == string::npos &&
            descr.find("Wireless") == string::npos && descr.find("Wi-Fi") == string::npos) {

//            int ipAddr;
            hostAddrStr = ipInfo->IpAddressList.IpAddress.String;
            inet_pton(AF_INET, ipInfo->IpAddressList.IpAddress.String, &hostAddr);

            LOG("network interface: name=%s, addr=%s, mask=%s\n", ipInfo->Description, hostAddrStr.c_str(), ipInfo->IpAddressList.IpMask.String);
            if (hostAddr)
                break;
        }
        
        ipInfo = ipInfo->Next;
    }
#else


//    getMyIp();
#endif
    
#if 0    
    printf("ipInfoLen = %d\n", (int)ipInfoLen);

    LOG("argc=%d, argv0=%s, argv1=%s\n", argc, argv[0], argv[1]);
    if (argc != 2) {
        printf("need hls host addr, mcast addr and port as one string: hlsHostAddr:mcastAddr:mcastPort\n"
               "ex: 127.0.0.1:224.1.1.100:5454\n");
        LOG("need hls host addr, mcast addr and port as one string: hlsHostAddr:mcastAddr:mcastPort\n"
               "ex: 127.0.0.1:224.1.1.100:5454\n");
        exit(1);
    }
    deque<string> args;
    stringtokquote(args, argv[1], ":");

    if (args.size() != 3) {
        printf("need hls host addr, mcast addr and port as one string: hlsHostAddr:mcastAddr:mcastPort\n"
               "ex: 127.0.0.1:224.1.1.100:5454\n");
        exit(1);
    }
#endif

    LOG("hostAddr = %s\n", hostAddrStr.c_str());
    mcastRelay.InitWebServer();
    
    threadIgmpReportsHandle = thread(&McastRelay::ThreadIgmpReports, this);
    threadIgmpReportsHandle.detach();

	while (1) {
#ifdef _WIN32
		Sleep(3000);
#else
		sleep(3);
#endif
        if (!webSockConn)
            break;
	}
    LOG("Exiting\n");
    exit(0);
    
    return 1;
}

#if 0
int check_wireless(const char* ifname, char* protocol) 
{
    int sock = -1;
    struct iwreq pwrq;
    memset(&pwrq, 0, sizeof(pwrq));
    strncpy(pwrq.ifr_name, ifname, IFNAMSIZ);

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        return 0;
    }

    if (ioctl(sock, SIOCGIWNAME, &pwrq) != -1) {
        if (protocol) strncpy(protocol, pwrq.u.name, IFNAMSIZ);
        close(sock);
        return 1;
    }

    close(sock);
    return 0;
}


int getMyIp() 
{
    struct ifaddrs *ifaddr, *ifa;

    if (getifaddrs(&ifaddr) == -1) {
        perror("getifaddrs");
        return -1;
    }

    /* Walk through linked list, maintaining head pointer so we
       can free list later */
    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
        char protocol[IFNAMSIZ]  = {0};

        if (ifa->ifa_addr == NULL ||
            ifa->ifa_addr->sa_family != AF_PACKET) continue;

        if (check_wireless(ifa->ifa_name, protocol)) {
            printf("interface %s is wireless: %s\n", ifa->ifa_name, protocol);
        } else {
            printf("interface %s is not wireless\n", ifa->ifa_name);
        }
    }

    freeifaddrs(ifaddr);
    return 0;
}
#endif

int McastRelay::InitWebServer()
{

	const char *options[] = {
	    "document_root", DOCUMENT_ROOT,
	    "listening_ports", PORT,
        "num_threads", "4",
	    "request_timeout_ms", "10000",
	    "error_log_file", "error.log",
	    "enable_auth_domain_check", "no",
        "enable_directory_listing", "no",
        "additional_header", "Access-Control-Allow-Origin: *",  // if more headers, put \r\n between them
	    0};
	struct mg_callbacks callbacks;
	struct mg_context *ctx;
	struct mg_server_ports ports[32];
	int port_cnt, n;

    LOG("Starting web server\n");

    webSockConn = 0;
    stopUdpRcvs = false;

    contCountTs = 0;
    goodContTs = 0;

	/* Start CivetWeb web server */
	memset(&callbacks, 0, sizeof(callbacks));
	callbacks.log_message = log_message;
	ctx = mg_start(&callbacks, 0, options);

	/* Check return value: */
	if (ctx == NULL) {
		LOG("Cannot start CivetWeb - mg_start failed.\n");
		return EXIT_FAILURE;
	}

	mg_set_request_handler(ctx, "/hlsMcast.m3u8$", hlsHandler, 0);
	mg_set_request_handler(ctx, "/*.ts$", tsHandler, 0);
	mg_set_request_handler(ctx, "/relayReady$", readyHandler, 0);

    subprots.nb_subprotocols = 1;
    const char *subprot = "ZvMcastRelay";
    subprots.subprotocols = (const char **)malloc(sizeof(char *));
    subprots.subprotocols[0] = subprot;
	mg_set_websocket_handler_with_subprotocols(ctx, "/", &subprots, webSocketConnectHandler, webSocketReadyHandler,
                                               websocketDataHandler, webSocketCloseHandler, 0);

	/* List all listening ports */
	memset(ports, 0, sizeof(ports));
	port_cnt = mg_get_server_ports(ctx, 32, ports);
	LOG("Server has %i listening ports:\n", port_cnt);

	for (n = 0; n < port_cnt && n < 32; n++) {
		const char *proto = ports[n].is_ssl ? "https" : "http";
		const char *host;

		if ((ports[n].protocol & 1) == 1) {
			/* IPv4 */
			host = "127.0.0.1";
			LOG("Browse files at %s://%s:%i/\n", proto, host, ports[n].port);
		}
	}

    return 1;
}

int McastRelay::StartMcastReceive()
{
    int sock;

    for (int i=0; i < MaxTsFiles; i++)
        memTsFiles[i].buf = new char [MaxTsFileSize];
    firstSequenceNum = 0;
    lastSequenceNum = 0;
    fileWriteIndex = 0;
    ourIp = 0;
    ourMask = 0;

    if (!(sock = GetMcastSock())) {
        LOG("Could not get main mcast socket\n");
        exit(1);
    }

    threadRcvHandle = thread(&McastRelay::ThreadMcastRcv, this, sock);
    threadRcvHandle.detach();

    return 1;
}

// firstSequenceNum is effectively the read pointer, but not mod ring size
// fileWriteIndex is the write index and it is mod the ring size
//
// Example:
//  MaxFiles=5  (4 external; one file is not visible -- being written into)
//  firstSeqNum   0 0 0 0 0 1 2 3 4 5 6 7 8 9 10 11
//  read index    0 0 0 0 0 1 2 3 4 0 1 2 3 4 0  1 ...
//  fileIndex     0 1 2 3 4 0 1 2 3 4 0 1 2 3 4  0 1 2 3 4 0 ...
//
void McastRelay::ThreadMcastRcv(int sock)
{
    socklen_t addrlen;
    struct sockaddr_in addr;
    int nbytes;
    char buf[4096];
    int contCount = 0, goodUdp = 0;

    vidFrames = 0;
    totBytes = 0;

    ringFull = false;
    
    prevTime = high_resolution_clock::now();

    LOG("Starting mcast receive thread\n");

    int len;
    socklen_t sz = sizeof(len);
    if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&len, &sz) < 0) {
        perror(": getsockopt");
    }
#if 1
    len = 1024*1024;
    if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&len,  sz) == -1) {
        perror(": setsockopt");
    }
    if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&len, &sz) < 0) {
        perror(": getsockopt");
    }
#endif
    
    LOG("Entering mcast receive loop\n");
    addrlen = sizeof(addr);
    while (1) {
        if ((nbytes = (int)recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&addr, &addrlen)) < 0) {
            LOG("ThreadMcastRcv:: receive failed, %s\n", strerror(errno));
            return;
        }

        if (firstMcastPkt)
            LOG("Initial mcast pkt received\n");
        firstMcastPkt = false;

        if (stopUdpRcvs)
            continue;

        if ((nbytes % 188) != 0) {
            LOG("Not even ts pkts!\n");
        }

        char *bufPart = buf;
        int tsPkts = 0;
        for (int i = 0; i < nbytes; i += 188) {
            int tsHeader = ntohl(*(int *)&buf[i]);
            if ((tsHeader & 0xff000000) !=  0x47000000){
                LOG("No start code!\n");
            }

            if (tsHeader & 0x400000 && ((tsHeader & 0x1fff00) >> 8) == vidPid) {

                int newFrame = true;
                WriteFile(bufPart, tsPkts * 188, newFrame);
                bufPart += tsPkts * 188;
                tsPkts = 0;

                vidFrames++;
            }
            tsPkts++;

            if ((tsHeader & 0x30) & 0x10 &&
                ((tsHeader & 0x1fff00) >> 8) == vidPid) {
                
                uint8_t pktContCount = tsHeader & 0xf;
                if (pktContCount != contCount) {
                    LOG("discontunuity, index = %d, pktCC = %x, expectedCC = %x; prior good udp %d\n", i, pktContCount, contCount, goodUdp);
                    contCount = pktContCount;
                    goodUdp = 0;
                }
                contCount++;
                if (contCount == 0x10)
                    contCount = 0;
            }
        }
        if (tsPkts) {
            int newFrame = false;
            WriteFile(bufPart, tsPkts * 188, newFrame);
        }
        goodUdp++;
    }
}

void McastRelay::ThreadIgmpReports()
{
    // No presently known way to do this on windows -- requires the user to ok admin access each time app runs
    // Btw, also tried closing/opening socket on mcast, but kernel too smart since if one socket is open on an mcast
    // it does not send another report.
    igmpSock = -1;

    if ((igmpSock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) 
        LOG("StartMcastReceive -- error, get IGMP socket error, most likely because not root; WARNING: requires IGMP Proxy if snooping switch present \n");
    int broadcast=1;
    if (igmpSock >= 0 && setsockopt(igmpSock, SOL_SOCKET, SO_BROADCAST, (char *)&broadcast, sizeof(broadcast)) == -1) {
        LOG("StartMcastReceive -- error, set socket bcast, %s) \n", strerror(errno));
//        close(igmpSock);
        return;
    }
    int loopCount = 0;
    while (1) {
        if (igmpSock >= 0 && (loopCount++ % 5) == 0 )
            SendIgmpReport();

        FILE *file = fopen("break", "r");
        if(file) {
            fclose(file);
            remove("break");
            gdb();
        }
        
        file = fopen("stop", "r");
        if(file) {
            fclose(file);
            remove("stop");
            exit(0);
        }

        file = fopen("dump", "r");
        if(file) {
            fclose(file);
            remove("dump");
            stopUdpRcvs = true;
        }
        
#ifdef _WIN32
		Sleep(1000);
#else
		sleep(1);
#endif
    }
}

void McastRelay::WriteFile(char *buf, int nbytes, int newFrame)
{
    mtx.lock();
        
    auto timeNow = high_resolution_clock::now();
//    duration<double, std::milli> diffMs = timeNow - prevTime;

    int diffMsVid = vidFrames * 1000/60.;

    if (totBytes + nbytes < MaxTsFileSize) {
        memcpy(&memTsFiles[fileWriteIndex].buf[totBytes], buf, nbytes);
        totBytes += nbytes;
    }
    else {
        LOG("ERROR -- File exceeded; dropping frame data\n");
    }

    // Pack into "files" that are less than MaxDurationSecs
    // Remember, the mcast stream being received is real time
    // diffMs.count()
    if (newFrame && (diffMsVid > (MaxTsDurationSecs * 1000 * 90 / 100) || totBytes + nbytes > (MaxTsFileSize - MaxFrameSize))) {
        prevTime = timeNow;
        vidFrames = 0;

        if ((totBytes % 188) != 0) {
            LOG("File-complete: Not even ts pkts!\n");
        }
        // File full..., save info and move to next file position
        memTsFiles[fileWriteIndex].durationMs = diffMsVid; // diffMs.count() + 800;
//            memTsFiles[fileWriteIndex].durationMs = 5900;
        memTsFiles[fileWriteIndex].bytes = totBytes;
        snprintf(memTsFiles[fileWriteIndex].name, sizeof(memTsFiles[fileWriteIndex].name)-1, "hlsMcast_%d.ts", lastSequenceNum);
//        LOG("wrote file %d, %d bytes, %s\n", fileWriteIndex, totBytes, memTsFiles[fileWriteIndex].name);

        lastSequenceNum++;
//        if (++lastSequenceNum == MaxTsFiles - 1)
//            lastSequenceNum = 0;

        if (++fileWriteIndex == MaxTsFiles) {
            fileWriteIndex = 0;
            ringFull = true;
//            LOG("LOOPING!!\n");
        }
        memTsFiles[fileWriteIndex].name[0] = 0;

        // If ring is full, then need to "discard" oldest entry by moving up the read pointer
        // Remember, need to do sequenceNum mod MaxTsFiles to get actual read index
        if (ringFull) 
            firstSequenceNum++;
            
        totBytes = 0;
    }
        
    mtx.unlock();
}

void printSS(stringstream &s)
{
    LOG("%s\n", s.str().c_str());
}

int McastRelay::ReadyHandler(struct mg_connection *conn, void *cbdata)
{
    mg_send_http_error(conn, 404, "%s", "Error: File not found");
    return 1;
}


int McastRelay::HlsHandler(struct mg_connection *conn, void *cbdata)
{
//    if (lastSequenceNum == 0 && !ringFull) {
    if (lastSequenceNum < 2) {
		mg_send_http_error(conn, 404, "%s", "Error: File not found");
        return 1;
    }

#if 0    
    if (!ourIp) {
        const struct mg_request_info *reqInfo = mg_get_request_info(conn);
        int ipaddr;
        inet_pton(AF_INET, reqInfo->remote_addr, (in_addr *)&ipaddr);
        
        GetOurIp(ipaddr);
        char addrStr[16];
        inet_ntop(AF_INET, &ourIp, addrStr, sizeof(addrStr));
        LOG("ThreadMcastRcv -- our IP %s\n", addrStr);
    }
#endif
    
    mtx.lock();
    
    // Create m3u8 file
    content.str("");
    content << "#EXTM3U\r\n"
//            << "#EXT-X-VERSION:3\r\n"
            << "#EXT-X-TARGETDURATION:" << MaxTsDurationSecs << "\r\n"
            << "#EXT-X-DISCONTINUITY" << "\r\n"
            << "#EXT-X-MEDIA-SEQUENCE:" << firstSequenceNum << "\r\n";

    // Do not provide the last one -- the one being written into
    for (int i = firstSequenceNum % MaxTsFiles; i != fileWriteIndex;) {
        content << "#EXTINF:" << memTsFiles[i].durationMs / 1000. << "00,\n"
                << "http://" << hostAddrStr << ":8154/" << memTsFiles[i].name << "\n";
        if (++i == MaxTsFiles)
            i = 0;
    }

    mtx.unlock();
    
    // And send it...
    mg_send_http_ok(conn, "application/vnd.apple.mpegurl", content.str().size());   // no cache resp
    mg_printf(conn, "%s", content.str().c_str());
    
	return 1;
}

int McastRelay::TsHandler(struct mg_connection *conn, void *cbdata)
{
	const struct mg_request_info *req_info = mg_get_request_info(conn);

    string filename(&req_info->local_uri[1]);

    mtx.lock();
    
    int i;
    for (i=0; i < MaxTsFiles; i++)
        if (filename == memTsFiles[i].name)
            break;

    string m3u8 = content.str();
    mtx.unlock();

    if (firstTsRead)
        LOG("Initial TS read\n");
    firstTsRead = false;
    
//    LOG("ts read %s, index=%d, firstSequenceNum=%d, lastSequenceNum=%d, fileWriteIndx=%d\n", filename.c_str(), i, firstSequenceNum, lastSequenceNum, fileWriteIndex);
//    printf("%s\n", m3u8.c_str());

    if (i == MaxTsFiles) {
		mg_send_http_error(conn, 404, "%s", "Error: File not found");
        LOG("Requested TS file not found %s\n", filename.c_str());
        return 1;
    }

    if ((memTsFiles[i].bytes % 188) != 0) {
        LOG("TS-read: Not even ts pkts!\n");
    }
    
    mg_send_http_ok(conn, "video/MP2T", memTsFiles[i].bytes);   // no cache resp

    int rc;
    if ((rc = mg_write(conn, memTsFiles[i].buf, memTsFiles[i].bytes)) != memTsFiles[i].bytes) {
        LOG("TS pkt send error %d\n", rc);
    }

    for (int j = 0; j < memTsFiles[i].bytes; j += 188) {
        int tsHeader = ntohl(*(int *)&memTsFiles[i].buf[j]);
        if ((tsHeader & 0xff000000) !=  0x47000000){
            LOG("Ts pkt no start code\n");
        }

        if ((tsHeader & 0x30) & 0x10 &&
            ((tsHeader & 0x1fff00) >> 8) == vidPid) {
                
            uint8_t pktContCount = tsHeader & 0xf;
            if (pktContCount != contCountTs) {
                LOG("TS pkt discontunuityTS, contCount=%d, good %d\n", contCountTs, goodContTs);
                contCountTs = pktContCount;
                goodContTs = 0;
            }
            else
                goodContTs++;
            contCountTs++;
            if (contCountTs == 0x10)
                contCountTs = 0;
        }
    }

    return 1;
}

int webSocketConnectHandler(const struct mg_connection *conn, void *userData)
{
    return mcastRelay.WebSocketConnectHandler(conn, userData);
}
void webSocketReadyHandler(struct mg_connection *conn, void *userData)
{
    mcastRelay.WebSocketReadyHandler(conn, userData);
}
int websocketDataHandler(struct mg_connection *conn, int bits, char *data, size_t len, void *userData)
{
    return mcastRelay.WebsocketDataHandler(conn, bits, data, len, userData);
}
void websocketSend(struct mg_context *ctx)
{
     mcastRelay.WebsocketSend(ctx);
}
void webSocketCloseHandler(const struct mg_connection *conn, void *userData)
{
    mcastRelay.WebSocketCloseHandler(conn, userData);
}

int McastRelay::WebSocketConnectHandler(const struct mg_connection *conn, void *userData)
{
//	struct mg_context *ctx = mg_get_context(conn);

#if 0 // not supported and maybe should not be checked. Up to the client to make sure the prot is accepted    
    if (!conn->acceptedWebSocketSubprotocol) {
        LOG("ERROR: Websocket connection received, but wrong protocol\n");
        return 0;
    }
#endif
    
    if (mcastAddr) {
        LOG("ERROR: Websocket connection received, but one already exists\n");
        return 1;   // NOTE!!! <<< ERROR ==> 1 
    }

    webSockConn = conn;
    // If we want userData, then do something like:
//    mg_set_user_connection_data(conn, userData);
  
    LOG("Websocket connection received\n");
	return 0;    // NOTE!!! <<< SUCCESS ==> 0
}


void McastRelay::WebSocketReadyHandler(struct mg_connection *conn, void *userData)
{
	const char *text = "relay version 1.4.3";

	mg_websocket_write(conn, MG_WEBSOCKET_OPCODE_TEXT, text, strlen(text));
	LOG("Websocket sent version string %s\n", text);
}


int McastRelay::WebsocketDataHandler(struct mg_connection *conn, int bits, char *data, size_t len, void *userDdata)
{
    string op;
	switch (((unsigned char)bits) & 0x0F) {
	case MG_WEBSOCKET_OPCODE_CONTINUATION:
		op = "continuation";
		break;
	case MG_WEBSOCKET_OPCODE_TEXT:
    {
		op = "text";

        deque<string> args;
        stringtokquote(args, data, ":");

        if (args.size() >= 2) {
            inet_pton(AF_INET, args[0].c_str(), (in_addr *)&mcastAddr);
            mcastPort = atoi(args[1].c_str());

            if (args.size() == 3) {
                vidPid = strtol(args[2].c_str(), NULL, 16);
            }
            else
                vidPid = 0x100;

            LOG("Websocket received mcast stream info: mcastAddr=%s, port=%d, vidPid(%s)=%x\n", args[0].c_str(), mcastPort, args.size() == 2 ? "default" : "passed-in",  vidPid);
    
            if (!StartMcastReceive()) {
                LOG("StartMcastReceive failed -- exiting\n");
                exit(1);
            }
        }
		break;
    }
	case MG_WEBSOCKET_OPCODE_BINARY:
		op = "binary";
		break;
	case MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE:
		op = "close";
		break;
	case MG_WEBSOCKET_OPCODE_PING:
		op = "ping";
		break;
	case MG_WEBSOCKET_OPCODE_PONG:
		op = "pong";
		break;
	default:
		op = "unknown";
		break;
	}
	LOG("Websocket got %lu bytes of %s\n", (unsigned long)len, op.c_str());

	return 1;
}

void McastRelay::WebsocketSend(struct mg_context *ctx)
{
    const char *text = "nothing to say";
    LOG("WebsocketSend\n");
	mtx.lock();
    mg_websocket_write((struct mg_connection *)webSockConn, MG_WEBSOCKET_OPCODE_TEXT, text, strlen(text));
	mtx.unlock();
}


void McastRelay::WebSocketCloseHandler(const struct mg_connection *conn, void *userData)
{
//	struct mg_context *ctx = mg_get_context(conn);
    LOG("Websocket exited, so are we\n");
    exit(0);
}

int McastRelay::GetMcastSock()
{
    int sock;
    
	if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		LOG("StartMcastReceive:: error cannot create socket, %s\n", strerror(errno));
		return 0;
	}

    int sockopt = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&sockopt, sizeof(sockopt)) < 0) {
		LOG("StartMcastReceive:: error setsockopt (2) failed %s\n", strerror(errno));
        return 0;
    }
        
	struct sockaddr_in myaddr, *myaddrp;
	memset((void *)&myaddr, 0, sizeof(myaddr));
	myaddr.sin_family = AF_INET;
#ifdef _WIN32   
    myaddr.sin_addr.s_addr = hostAddr;
#else    
    myaddr.sin_addr.s_addr = INADDR_ANY;
#endif    
	myaddr.sin_port = htons(mcastPort);
    myaddrp = &myaddr;

    if (::bind(sock, (struct sockaddr *)myaddrp, sizeof(myaddr)) < 0) {
		LOG("StartMcastReceive:: error bind failed, %s\n", strerror(errno));
		return 0;
	}

    struct ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = mcastAddr;
#ifdef _WIN32   
    mreq.imr_interface.s_addr = hostAddr;
#else
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);
#endif
    if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq)) < 0) {
		LOG("StartMcastReceive:: multicast join failed, %s\n", strerror(errno));
        return 0;
     }

    return sock;
}

int McastRelay::SendIgmpReport()
{
    Pkt pkt;
    InitIpPkt(pkt);
    struct sockaddr_in dest;

    pkt.iphdr.ip_src.s_addr = ourIp;

    pkt.iphdr.ip_dst.s_addr = mcastAddr;
    pkt.iphdr.ip_sum = 0;
    pkt.iphdr.ip_sum = InetCksum((uint16_t *) &pkt.iphdr, IP4_HDRLEN);

    pkt.igmp.type = 0x16; // v2
    pkt.igmp.maxRespTime = 0;
    pkt.igmp.chksum = 0;
    pkt.igmp.mcastAddr = mcastAddr;
    pkt.igmp.chksum = InetCksum((unsigned short *)&pkt.igmp, sizeof(IgmpPkt));
 
    // The kernel is going to prepare layer 2 information (ethernet frame header) for us.
    // For that, we need to specify a destination for the kernel in order for it
    // to decide where to send the raw datagram. We fill in a struct in_addr with
    // the desired destination IP address, and pass this structure to the sendto() function.
    memset(&dest, 0, sizeof (struct sockaddr_in));
    dest.sin_family = AF_INET;
    dest.sin_addr.s_addr = pkt.iphdr.ip_dst.s_addr;

    if (sendto(igmpSock, (char *)&pkt, sizeof(pkt), 0, (struct sockaddr *)&dest, sizeof(dest)) < 0) {
        LOG("SendIgmpReport -- error, sendto() error, %s \n", strerror(errno));
        return 0;
    }
    return 1;
}

int McastRelay::InitIpPkt(Pkt &pkt)
{
    pkt.iphdr.ip_hl = IP4_HDRLEN / sizeof (uint32_t);
    pkt.iphdr.ip_v = 4;
    pkt.iphdr.ip_tos = 0;
    // Total length of datagram (16 bits): IP header + UDP header + datalen
#ifdef __APPLE__
    pkt.iphdr.ip_len = IP4_HDRLEN + sizeof(IgmpPkt);  // BSD A.K.A MACS !!!!!!
#else
    pkt.iphdr.ip_len = htons (IP4_HDRLEN + sizeof(IgmpPkt));
#endif
    // ID sequence number (16 bits): unused, since single datagram
    pkt.iphdr.ip_id = htons (0);
    // Flags, and Fragmentation offset (3, 13 bits): 0 since single datagram
    pkt.iphdr.ip_off = 0;
    // Time-to-Live (8 bits): default to maximum value
    pkt.iphdr.ip_ttl = 1;
    // Transport layer protocol (8 bits): 17 for UDP
    pkt.iphdr.ip_p = IPPROTO_IGMP;

    return 1;
}

#if 0
int McastRelay::GetOurIp(int remoteIp)
{
    struct ifaddrs *ifAddrs;
    struct ifaddrs *ifAddr;
    int localAddr = 0, globalAddr = 0;

    getifaddrs(&ifAddrs);
    if (!ifAddrs)  {
        printf("RcDeviceMgrHd::Init -- no ip addresses\n");
        exit(0); // don't cause app restart
    }

    for (ifAddr = ifAddrs; ifAddr != NULL; ifAddr = ifAddr->ifa_next) {
        if (!ifAddr->ifa_addr) {
            continue;
        }
        if (strstr(ifAddr->ifa_name, "docker"))
            continue;
        if (ifAddr->ifa_addr->sa_family == AF_INET) { // check it is IP4
            // is a valid IP4 Address
            int ipaddr = (int)((struct sockaddr_in *)ifAddr->ifa_addr)->sin_addr.s_addr;

            if (ipaddr == 0x100007f)
                continue;

            if ((ipaddr & 0xffff) != 0xfea9) {
                globalAddr = ipaddr;
//                globalMask = (int)((struct sockaddr_in *)ifAddr->ifa_netmask)->sin_addr.s_addr;;
            }
            else if ((ipaddr & 0xffff) == 0xfea9)  {
                localAddr = ipaddr;
            }
        }
    }
    freeifaddrs(ifAddrs);

    if (globalAddr) {
        ourIp = globalAddr;
        return 1;
    }
    ourIp = localAddr;

    return ifAddr != 0;
}
#endif

int McastRelay::InetCksum(uint16_t *addr, u_int len)
{
    int sum = 0;
    int nleft = (int)len;
    uint16_t *w = addr;
    uint16_t answer = 0;

    while (nleft > 1)  {
        sum += *w++;
        nleft -= 2;
    }

    if (nleft == 1) {
        *(uint8_t *) (&answer) = *(uint8_t *)w ;
        sum += answer;
    }

    // Add carry bits
    sum = (sum >> 16) + (sum & 0xffff);	 // add initial carries
    sum += (sum >> 16);			// add possible new carries
    answer = ~sum;				// compliment

    return answer;
}

int hlsHandler(struct mg_connection *conn, void *cbdata)
{
    return mcastRelay.HlsHandler(conn, cbdata);
}

int tsHandler(struct mg_connection *conn, void *cbdata)
{
    return mcastRelay.TsHandler(conn, cbdata);
}

int readyHandler(struct mg_connection *conn, void *cbdata)
{
    return mcastRelay.ReadyHandler(conn, cbdata);
}

int log_message(const struct mg_connection *conn, const char *message)
{
	puts(message);
	return 1;
}

#ifdef LOGGING

int logFileNumber = 1;
int debugMode;
int logInit;

void LOG(const char* fmt, ...) 
{
#ifdef _WIN32

    char path[MAX_PATH + 1];
    SHGetSpecialFolderPathA(NULL, path, CSIDL_APPDATA,FALSE );

    string newDir((string)path + "/zeeVee");

    CreateDirectoryA(newDir.c_str(), NULL);
    
    string logFilename = newDir + "/" + "mcastRelay.log";
    string logFilename1 = newDir + "/" + "mcastRelay1.log";
#else
    string logFilename = "/var/tmp/mcastRelay.log";
    string logFilename1 = "/var/tmp/mcastRelay1.log";
#endif

    if (!debugMode) {
        printf("LOG -- 1/n");
        // Limit size of log file
        if (logInit == 0) {
            if (freopen(logFilename.c_str(), "a", stdout) == 0)
                perror("LOG init -- Failed to reopen stdout");
            logInit = 1;
        }
        char filename[256];
        int size = (int)ftell(stdout);
        if (size > 5 * 1024 * 1024) {

            int maxSaved = 10;
            int lastFound = false;
            for (int i = maxSaved; i > 0; i--) {
                snprintf(filename, sizeof(filename), logFilename.c_str(), i);
                if (!lastFound) {
                    ifstream file(filename);
                    if(file) {
                        file.close();
                        lastFound = true;
                        if (i == maxSaved) {
                            unlink(filename);
                            continue;
                        }
                    }
                }

                if (lastFound) {
                    char nextHigherFilename[256];
                    snprintf(nextHigherFilename, sizeof(nextHigherFilename), logFilename.c_str(), i+1);
                    rename(filename, nextHigherFilename);
                }
            }
                
            rename(logFilename.c_str(), logFilename1.c_str());

            if ((freopen(logFilename.c_str(), "w", stdout)) == 0) {
                perror("LOG -- Failed to reopen log file");
            }
        }
    }

    time_t result = time(NULL);
    char *str = ctime(&result);
    str[strlen(str)-1] = 0;  // remove cr
    printf("%s  ", str);

#if 0  // not working and never on _WIN32
    // get void*'s for all entries on the stack
    void *array[6];
    size_t size;
    size = backtrace(array, sizeof(array) / sizeof(void *));

    cout << "(";
//    char **stackTrace = backtrace_symbols(array, size);
    for (u_int i=1; i < size; i++) {
//        printf("%s", stackTrace[i]);
        cout << array[i];
        if (i != size - 1)
            cout << ";";
    }
    cout << ")";
#endif  

    va_list args;
    va_start(args, fmt);
    vprintf(fmt, args);
    va_end(args);
    fflush(stdout);
}

#endif
