/***************************************************************************
 *
 * Project:  OpenCPN
 * Purpose:  PlugIn Manager Object
 * Author:   David Register
 *
 ***************************************************************************
 *   Copyright (C) 2010 by David S. Register                               *
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,  USA.         *
 **************************************************************************/
 //Originally by balp on 2018-07-28.

#ifdef __MINGW32__
#undef IPV6STRICT    // mingw FTBS fix:  missing struct ip_mreq
#include <windows.h>
#endif

#include "zeroconf.hpp"

#include "wx/wxprec.h"

#ifndef  WX_PRECOMP
#include "wx/wx.h"
#endif //precompiled headers

#include "wx/tokenzr.h"
#include <wx/datetime.h>

#include <stdlib.h>
#include <math.h>
#include <time.h>

#ifndef __WXMSW__
#include <arpa/inet.h>
#include <netinet/tcp.h>
#endif

#include <vector>
#include <sstream>
#include <wx/socket.h>
#include <wx/log.h>
#include <wx/memory.h>
#include <wx/chartype.h>
#include <wx/wx.h>
#include <wx/sckaddr.h>
#include "easywsclient.hpp"
#include "chart1.h"
#include "wxServDisc.h"

#if defined(__WXMSW__) && !defined(__MINGW32__)
#include <Ws2tcpip.h>           // for ip_mreq
#endif

#include "dychart.h"

#include "datastream.h"
#include "SignalKDataStream.h"
#include "NetworkDataStream.h"
#include "OCPN_SignalKEvent.h"
#include "OCPN_DataStreamEvent.h"


#if !defined(NAN)
static const long long lNaN = 0xfff8000000000000;
#define NAN (*(double*)&lNaN)
#endif

extern bool g_benableUDPNullHeader;

static wxEvtHandler *s_wsConsumer;
extern MyFrame         *gFrame;

#include <wx/sckstrm.h>
#include "wx/jsonreader.h"
#include "wx/jsonwriter.h"

// Handle nessages from WebSocket thread, forward to stipulated consumer, and manage watchdog
class OCPN_WebSocketMessageHandler : public wxEvtHandler
{
 public:
     OCPN_WebSocketMessageHandler( SignalKDataStream *parent, wxEvtHandler *upstream_consumer );
    ~OCPN_WebSocketMessageHandler();
    
    void OnWebSocketMessage( OCPN_SignalKEvent& event );

    SignalKDataStream   *m_parent;
    wxEvtHandler        *m_upstream_consumer;
};



OCPN_WebSocketMessageHandler::OCPN_WebSocketMessageHandler( SignalKDataStream *parent, wxEvtHandler *upstream_consumer )
{
    m_upstream_consumer = upstream_consumer;
    m_parent = parent;
    Bind(EVT_OCPN_SIGNALKSTREAM, &OCPN_WebSocketMessageHandler::OnWebSocketMessage, this);
}

void OCPN_WebSocketMessageHandler::OnWebSocketMessage( OCPN_SignalKEvent &event )
{
    if(m_upstream_consumer){
            OCPN_SignalKEvent signalKEvent(0, EVT_OCPN_SIGNALKSTREAM, event.GetString());
            m_upstream_consumer->AddPendingEvent(signalKEvent);
    }
    
    m_parent->ResetWatchdog();      // feed the dog

}

OCPN_WebSocketMessageHandler::~OCPN_WebSocketMessageHandler()
{
    Unbind(EVT_OCPN_SIGNALKSTREAM, &OCPN_WebSocketMessageHandler::OnWebSocketMessage, this);
}    




BEGIN_EVENT_TABLE(SignalKDataStream, wxEvtHandler)
                EVT_TIMER(TIMER_SOCKET + 2, SignalKDataStream::OnTimerSocket)
                EVT_SOCKET(SIGNALK_SOCKET_ID, SignalKDataStream::OnSocketEvent)
                EVT_TIMER(TIMER_SOCKET + 3, SignalKDataStream::OnSocketReadWatchdogTimer)
END_EVENT_TABLE()

SignalKDataStream::SignalKDataStream(wxEvtHandler *input_consumer,
                      const ConnectionParams *params)
            : DataStream(input_consumer, params),
              m_params(params),
              m_sock(0),
              m_brx_connect_event(false)

{
        m_addr.Hostname(params->NetworkAddress);
        m_addr.Service(params->NetworkPort);
        m_socket_timer.SetOwner(this, TIMER_SOCKET + 2);
        m_socketread_watchdog_timer.SetOwner(this, TIMER_SOCKET + 3);
        m_useWebSocket = true;
        m_wsThread = NULL;
        m_threadActive = false;
        m_eventHandler = new OCPN_WebSocketMessageHandler( this, GetConsumer());

        
        Open();

}

SignalKDataStream::~SignalKDataStream(){

    if(m_useWebSocket){
        delete m_eventHandler;
        m_eventHandler = NULL;
        s_wsConsumer = NULL;
    }
    else{
        if (GetSock()->IsOk()){
            char unsub[]  = "{\"context\":\"*\",\"unsubscribe\":[{\"path\":\"*\"}]}\r\n";
            GetSock()->Write(unsub, strlen(unsub));
        }
    }

    Close();
}

void SignalKDataStream::Open(void) {
    
    wxString discoveredIP;
    int discoveredPort;
    
    if(m_useWebSocket){
        std::string serviceIdent = std::string("_signalk-ws._tcp.local.");              // Works for node.js server
        if(m_params->AutoSKDiscover){
            if( DiscoverSKServer( serviceIdent, discoveredIP, discoveredPort, 1) ) {       // 1 second scan
                wxLogDebug(wxString::Format(_T("SK server autodiscovery finds WebSocket service: %s:%d"), discoveredIP.c_str(), discoveredPort));
                m_addr.Hostname(discoveredIP);
                m_addr.Service(discoveredPort);
                
                // Update the connection params, by pointer to item in global params array
                ConnectionParams *params = (ConnectionParams *)m_params;        // non-const
                params->NetworkAddress = discoveredIP;
                params->NetworkPort = discoveredPort;
            }
            else
                wxLogDebug(_T("SK server autodiscovery finds no WebSocket server."));
        }

        OpenWebSocket();
    }
    else{
        std::string serviceIdent = std::string("_signalk-ws._tcp.local");             // Works for node.js server
        if(m_params->AutoSKDiscover){
            if( DiscoverSKServer( serviceIdent, discoveredIP, discoveredPort, 1) ) {       // 1 second scan
                wxLogMessage(wxString::Format(_T("SK server autodiscovery finds REST service: %s:%d"), discoveredIP.c_str(), discoveredPort));
                m_addr.Hostname(discoveredIP);
                m_addr.Service(discoveredPort);
            }
            else
                wxLogMessage(_T("SK server autodiscovery finds no REST server."));
        }

        OpenTCPSocket();
    }
}

bool SignalKDataStream::DiscoverSKServer( std::string serviceIdent, wxString &ip, int &port, int tSec){
#if 0    
    std::vector<Zeroconf::mdns_responce> result;
    bool st = Zeroconf::Resolve(serviceIdent.c_str(), tSec, &result); 

    for(size_t i = 0 ; i < result.size() ; i++){
      sockaddr_storage sas = result[i].peer;                 // Address of the responded machine
      sockaddr_in *sai = (sockaddr_in*)&sas;
      ip = wxString(inet_ntoa(sai->sin_addr));
      
      std::vector<uint8_t> data = result[i].data;

      std::vector<Zeroconf::Detail::mdns_record> records = result[i].records;
      for(size_t j = 0 ; j < records.size() ; j++){

        uint16_t type = records[j].type;
        if(type == 33){                                 // SRV
            size_t pos = records[j].pos;
            //size_t len = records[j].len;
            //std::string name = records[j].name;
            
            // TODO This is pretty ugly, but I cannot find a definition of SRV record
            unsigned char portHi = data[pos + 16];
            unsigned char portLo = data[pos + 17];
            port = (portHi * 256) + portLo;
            return true;
        }
      }
    }
    return false;
#else
    wxServDisc *servscan = new wxServDisc(gFrame, wxString(serviceIdent.c_str()), QTYPE_PTR);
    
    for(int i = 0 ; i < 10 ; i++){
        if(servscan->getResultCount()){
            auto result = servscan->getResults().at(0);
            delete servscan;
            //wxSleep(5);

            //return false;
            wxServDisc *namescan = new wxServDisc(0, result.name, QTYPE_SRV);
            for(int j=0 ; j < 10 ; j++){
                if(namescan->getResultCount()){
                    auto namescanResult = namescan->getResults().at(0);
                    port = namescanResult.port;
                    delete namescan;

                    wxServDisc *addrscan = new wxServDisc(0, namescanResult.name, QTYPE_A);
                    for(int k=0 ; k < 10 ; k++){
                        if(addrscan->getResultCount()){
                            auto addrscanResult = addrscan->getResults().at(0);
                            ip = addrscanResult.ip;
                            delete addrscan;
                            return true;
                            break;
                        }
                        else{
                            wxYield();
                            wxMilliSleep(1000);
                        }
                    }
                    delete addrscan;
                    return false;
                }
                else{
                    wxYield();
                    wxMilliSleep(1000);
                }
            }
            delete namescan;
            return false;
        }
        else{
            wxYield();
            wxMilliSleep(1000);
        }
    }
    
    delete servscan;
    return false;

    //wxString a = servscan.getResults().at(0).ip;
    //wxServDisc namescan(0, servscan->getResults().at(0).name, QTYPE_SRV);
    //wxServDisc addrscan(0, namescan.getResults().at(0).name, QTYPE_A);
    //wxString addr = addrscan.getResults().at(0).ip;
#endif    
}    

void SignalKDataStream::OpenTCPSocket()
{
    wxLogMessage(wxString::Format(_T("Opening Signal K TCPSocket client: %s"),
            m_params->GetDSPort().c_str()));

    SetSock(new wxSocketClient());
    GetSock()->SetEventHandler(*this, SIGNALK_SOCKET_ID);
    GetSock()->SetNotify(wxSOCKET_CONNECTION_FLAG | wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG);
    GetSock()->Notify(TRUE);
    GetSock()->SetTimeout(1);              // Short timeout

    SetConnectTime(wxDateTime::Now());

    wxSocketClient* tcp_socket = static_cast<wxSocketClient*>(GetSock());
    tcp_socket->Connect(GetAddr(), FALSE);
    SetBrxConnectEvent(false);

}

int sdogval;
void SignalKDataStream::OnSocketReadWatchdogTimer(wxTimerEvent& event)
{
    m_dog_value--;
    sdogval++;
    
    if( m_dog_value <= 0 ) {            // No receive in n seconds, assume connection lost
        if(m_useWebSocket){
            wxLogMessage( wxString::Format(_T("    WebSocket SignalKDataStream watchdog timeout: %s"), GetPort().c_str()) );
        
            //printf("DOGTIME  %d\n", sdogval);
            CloseWebSocket();
            OpenWebSocket();
            SetWatchdog( N_DOG_TIMEOUT_RECONNECT );
        }
        else{
            wxLogMessage( wxString::Format(_T("    TCP SignalKDataStream watchdog timeout: %s"), GetPort().c_str()) );

            if(GetProtocol() == TCP ) {
                wxSocketClient* tcp_socket = dynamic_cast<wxSocketClient*>(GetSock());
                if(tcp_socket) {
                    tcp_socket->Close();
                }
                GetSocketTimer()->Start(5000, wxTIMER_ONE_SHOT);    // schedule a reconnect
                GetSocketThreadWatchdogTimer()->Stop();
            }
        }
    }

    GetSocketThreadWatchdogTimer()->Start(1000, wxTIMER_ONE_SHOT);    // re-Start the dog
}

void SignalKDataStream::OnTimerSocket(wxTimerEvent& event)
{
    //  Attempt a connection
    wxSocketClient* tcp_socket = dynamic_cast<wxSocketClient*>(GetSock());
    if(tcp_socket) {
        if(tcp_socket->IsDisconnected() ) {
            SetBrxConnectEvent(false);
            tcp_socket->Connect(GetAddr(), FALSE);
            GetSocketTimer()->Start(5000, wxTIMER_ONE_SHOT);    // schedule another attempt
        }
    }
}

void SignalKDataStream::OnSocketEvent(wxSocketEvent& event)
{
#define RD_BUF_SIZE    4096


    switch(event.GetSocketEvent())
    {
        case wxSOCKET_CONNECTION :
        {
            wxLogMessage( wxString::Format(_T("SignalKDataStream connection established: %s"),
                                           GetPort().c_str()) );
            ResetWatchdog();                // feed the dog
            if (GetSock()->IsOk())
                (void) SetOutputSocketOptions(GetSock());
            GetSocketTimer()->Stop();
            SetBrxConnectEvent(true);
            SetConnectTime(wxDateTime::Now());
            
            char sub2[]  = "{\"context\":\"vessels.self\",\"subscribe\":[{\"path\":\"navigation.*\"}]}\r\n";
            GetSock()->Write(sub2, strlen(sub2));

            break;
        }

        case wxSOCKET_LOST:
        {

            if (GetBrxConnectEvent())
                wxLogMessage(wxString::Format(_T("SignalKDataStream connection lost: %s"), GetPort().c_str()));
            wxDateTime now = wxDateTime::Now();
            wxTimeSpan since_connect = now - GetConnectTime();

            int retry_time = 5000;          // default

            //  If the socket has never connected, and it is a short interval since the connect request
            //  then stretch the time a bit.  This happens on Windows if there is no dafault IP on any interface

            if(!GetBrxConnectEvent() && (since_connect.GetSeconds() < 5) )
                retry_time = 10000;         // 10 secs

            GetSocketThreadWatchdogTimer()->Stop();
            GetSocketTimer()->Start(retry_time, wxTIMER_ONE_SHOT);     // Schedule a re-connect attempt

            break;
        }

        case wxSOCKET_INPUT:
        {
            #define RD_BUF_SIZE    4096 // Allows handling of high volume data streams.
            wxJSONReader jsonReader;
            wxJSONValue root;

            std::vector<char> data(RD_BUF_SIZE+1);
            event.GetSocket()->Read(&data.front(),RD_BUF_SIZE);
            if(!event.GetSocket()->Error())
            {
                size_t count = event.GetSocket()->LastCount();
                m_sock_buffer.append(&data.front(), count);
            }

            bool done = false;

            while(!done){
                int sk_tail = 2;
                size_t sk_end = m_sock_buffer.find_first_of("\r\n"); // detect the end of an SK string by finding the EOL

                if (sk_end == wxString::npos) // No termination characters: continue reading
                    break;


                size_t bufl = m_sock_buffer.size();
                if(sk_end <= m_sock_buffer.size() - sk_tail){
                    std::string sk_line = m_sock_buffer.substr(0,sk_end + sk_tail);

                    //  If, due to some logic error, the {nmea_end} parameter is larger than the length of the
                    //  socket buffer, then std::string::substr() will throw an exception.
                    //  We don't want that, so test for it.
                    //  If found, the simple solution is to clear the socket buffer, and carry on
                    //  This has been seen on high volume TCP feeds, Windows only.
                    //  Hard to catch.....
                    if(sk_end > m_sock_buffer.size())
                        m_sock_buffer.clear();
                    else
                        m_sock_buffer = m_sock_buffer.substr(sk_end + sk_tail);

                    size_t sk_start = 0; 
                    if(sk_start != wxString::npos){
                        sk_line = sk_line.substr(sk_start);
                        if(sk_line.size()){
                            
                            int errors = jsonReader.Parse(sk_line, &root);
                            if (errors > 0) {
                                wxLogMessage(
                                            wxString::Format(_T("SignalKDataStream ERROR: the JSON document is not well-formed:%d: %s"),
                                                errors,
                                                GetPort().c_str()));

                            } else {
                                if( GetConsumer() ) {

#if 0                                    
                                    wxString dbg;
                                    wxJSONWriter writer;
                                    writer.Write(root, dbg);

                                    wxString msg( _T("SignalK TCP Socket Event sent to consumer:\n") );
                                    msg.append(dbg);
                                    wxLogMessage(msg);
#endif
                                    OCPN_SignalKEvent signalKEvent(0, EVT_OCPN_SIGNALKSTREAM, sk_line);
                                    GetConsumer()->AddPendingEvent(signalKEvent);
                                }
                            }
                        }
                    }
                }
                else
                    done = true;
            }

            // Prevent non-nmea junk from consuming to much memory by limiting carry-over buffer size.
            if(m_sock_buffer.size()>RD_BUF_SIZE)
                m_sock_buffer = m_sock_buffer.substr(m_sock_buffer.size()-RD_BUF_SIZE);

            ResetWatchdog();                // feed the dog

            break;
        }
        default :
            break;
    }

}


bool SignalKDataStream::SetOutputSocketOptions(wxSocketBase* sock)
{
    int ret;
    int nagleDisable=1;
    ret = sock->SetOption(IPPROTO_TCP,TCP_NODELAY,&nagleDisable, sizeof(nagleDisable));
    unsigned long outbuf_size = 1024;
    return (sock->SetOption(SOL_SOCKET,SO_SNDBUF,&outbuf_size, sizeof(outbuf_size)) && ret);
}

void SignalKDataStream::Close()
{
    if(m_useWebSocket){
        wxLogMessage( _T("Closing Signal K WebSocket DataStream ") );
        CloseWebSocket();
    }
    else{
        wxLogMessage( wxString::Format(_T("Closing Signal K DataStream %s"), GetPort().c_str()) );
    //    Kill off the TCP Socket if alive
        if(m_sock){
            m_sock->Notify(FALSE);
            m_sock->Destroy();
        }
    }

    m_socket_timer.Stop();
    m_socketread_watchdog_timer.Stop();


    DataStream::Close();
}

//      WebSocket implementation

class WebSocketThread : public wxThread
{ 
    public:     WebSocketThread( SignalKDataStream *parent, wxIPV4address address, wxEvtHandler *consumer );
                virtual void *Entry();
    private:
                static void HandleMessage(const std::string & message);

                wxIPV4address   m_address;
                wxEvtHandler *m_consumer;
                SignalKDataStream *m_parentStream;
};


WebSocketThread::WebSocketThread( SignalKDataStream *parent, wxIPV4address address, wxEvtHandler *consumer)
{
    m_address = address;
    m_consumer = consumer;
    m_parentStream = parent;
}

void *WebSocketThread::Entry()
{
    using easywsclient::WebSocket;
    
    m_parentStream->SetThreadRunning(true);
    
    s_wsConsumer = m_consumer;
    
    wxString host = m_address.IPAddress();
    int port = m_address.Service();
    
    // Craft the address string
    std::stringstream wsAddress;
    wsAddress << "ws://" << host.mb_str()  << ":" << port << "/signalk/v1/stream?subscribe=all&sendCachedValues=false" ;

    WebSocket::pointer ws = WebSocket::from_url(wsAddress.str());
    if(ws == NULL){
        //printf("No Connect\n");
        m_parentStream->SetThreadRunning(false);
        return 0;
    }
    while (true) {
        if(TestDestroy()){
            //printf("receiving delete\n");
	    ws->close();
        }
        
        if(ws->getReadyState() == WebSocket::CLOSED){
	    //printf("closed\n");
	    break;
	}
    	ws->poll(10);
        if(ws->getReadyState() == WebSocket::OPEN){
            ws->dispatch(HandleMessage);
        }
    }
    
    //printf("ws delete\n");
    delete ws; 

    m_parentStream->SetThreadRunning(false);

    return 0;
}

void WebSocketThread::HandleMessage(const std::string & message)
{
    if(s_wsConsumer){
        OCPN_SignalKEvent signalKEvent(0, EVT_OCPN_SIGNALKSTREAM, message);
        s_wsConsumer->AddPendingEvent(signalKEvent);
    }
   
}




void SignalKDataStream::OpenWebSocket()
{
    //printf("OpenWebSocket\n");
    wxLogMessage(wxString::Format(_T("Opening Signal K WebSocket client: %s"),
            m_params->GetDSPort().c_str()));
    
    // Start a thread to run the client without blocking
    
    m_wsThread = new WebSocketThread(this, GetAddr(), m_eventHandler);
    if ( m_wsThread->Create() != wxTHREAD_NO_ERROR ) {
        wxLogError(wxT("Can't create WebSocketThread!"));
        
        return;
    }

    ResetWatchdog();
    GetSocketThreadWatchdogTimer()->Start(1000, wxTIMER_ONE_SHOT);    // Start the dog

    m_wsThread->Run();
}

void SignalKDataStream::CloseWebSocket()
{
    if(m_wsThread){
        if(IsThreadRunning()){
            //printf("sending delete\n");
            m_wsThread->Delete();
            wxMilliSleep(100);
            
            int nDeadman = 0;
            while(IsThreadRunning() && (++nDeadman < 200)){   // spin for max 2 secs.
                wxMilliSleep(10);
            }
            //printf("Closed in %d\n", nDeadman);
            wxMilliSleep(100);
        }
    }
}
