/*
 * QT Client for X2GoKDrive
 * Copyright (C) 2018-2023 Oleksandr Shneyder <o.shneyder@phoca-gmbh.de>
 * Copyright (C) 2018-2023 phoca-GmbH
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

#ifndef CLIENT_H
#define CLIENT_H

#define KDRStdErr(a) Client::KDRStdErrFunc(a)<<__FILE__<<":"<< __LINE__<<":"<< __func__<<"():  "

//FEATURE_VERSION is not cooresponding to actual version of client
//it used to tell server which features are supported by client
//Changes 1 - 2: supporting extended selection and sending selection on demand
//Changes 2 - 3: support UDP protocol, sending keep alive packets
#define FEATURE_VERSION 3

//Version of client OS for same reason
enum OS_VERSION{OS_LINUX, OS_WINDOWS, OS_DARWIN};


#define EVLENGTH 41


#define Button1Mask (1<<8)
#define Button2Mask (1<<9)
#define Button3Mask (1<<10)
#define Button4Mask (1<<11)
#define Button5Mask (1<<12)


#define Button1 1
#define Button2 2
#define Button3 3
#define Button4 4
#define Button5 5


//Events
#define KEYPRESS 2
#define KEYRELEASE 3
#define MOUSEPRESS 4
#define MOUSERELEASE 5
#define MOUSEMOTION 6
#define GEOMETRY 7
#define UPDATE 8
#define SELECTIONEVENT 9
#define CLIENTVERSION 10
#define DEMANDSELECTION 11
#define KEEPALIVE 12
#define CACHEREBUILD 13
#define WINCHANGE 14
//client is going to disconnect
#define DISCONNECTCLIENT 15
//ask to resend particular frame
#define RESENDFRAME 16
//client is requesting UDP port for frames
#define OPENUDP 17


#define ShiftMask (1<<0)
#define LockMask (1<<1)
#define ControlMask (1<<2)
#define Mod1Mask (1<<3)
#define Mod2Mask (1<<4)
#define Mod3Mask (1<<5)
#define Mod4Mask (1<<6)
#define Mod5Mask (1<<7)

#ifdef Q_OS_WIN
#define WINCAPS 256
#define WINNUM 512
#endif

#define HEADER_SIZE 56
#define REGION_HEADER 64

//max size for UDP dgram
#define UDPDGRAMSIZE 1200

//UDP Server DGRAM Header - 4B checksum + 2B packet seq number + 2B amount of datagrams + 2B datagram seq number + 1B type
#define SRVDGRAMHEADERSIZE (4+2+2+2+1)


//check if server is alive every 30 seconds
#define SERVERALIVETIMEOUT 30//sec

//Types for UDP datagrams
enum ServerDgramType{
    ServerFramePacket, //dgram belongs to packet representing frame
    ServerRepaintPacket, // dgram belongs to packet with screen repaint and the loss can be ignored
};

enum SelectionMime{STRING,UTF_STRING,PIXMAP};
enum WinState{UNCHANGED, DELETED, ICONIFIED};
enum SelectionType{PRIMARY,CLIPBOARD};
enum ClipboardMode{CLIP_BOTH, CLIP_SERVER, CLIP_CLIENT, CLIP_NONE};

enum WinType{WINDOW_TYPE_DESKTOP, WINDOW_TYPE_DOCK, WINDOW_TYPE_TOOLBAR, WINDOW_TYPE_MENU, WINDOW_TYPE_UTILITY, WINDOW_TYPE_SPLASH,
    WINDOW_TYPE_DIALOG, WINDOW_TYPE_DROPDOWN_MENU, WINDOW_TYPE_POPUP_MENU, WINDOW_TYPE_TOOLTIP, WINDOW_TYPE_NOTIFICATION,
    WINDOW_TYPE_COMBO, WINDOW_TYPE_DND, WINDOW_TYPE_NORMAL};

#include <QMainWindow>
#include <QAbstractSocket>
#include <stdlib.h>
#include <QClipboard>
#include <stdio.h>
#include <QTextStream>



#ifdef Q_OS_LINUX
class XCBClip;
#endif

#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
#define KDR_ENDL Qt::endl>>Qt::dec
#define KDR_HEX Qt::hex
#define KDR_DEC Qt::dec
#else
#define KDR_ENDL endl>>dec
#define KDR_HEX hex
#define KDR_DEC dec
#endif


class FrameRegion
{
public:
    FrameRegion(uint32_t source_crc, int32_t source_x, int32_t source_y, int32_t x, int32_t y,
                uint32_t width, uint32_t height, uint32_t dataSize);
    ~FrameRegion();
    uint32_t source_crc,  width, height, dataSize;
    int32_t source_x, source_y, x, y;
    QPixmap pix;
};

class Frame
{
public:
    Frame(uint32_t width, uint32_t height, int32_t x, int32_t y,  uint32_t numOfRegions, uint32_t crc, uint32_t winId);
    ~Frame();
    uint32_t width, height, crc, numOfRegions, winId;
    int32_t x, y;
    QVector<FrameRegion*> regions;
};

class X2GoCursor
{
public:
    X2GoCursor(uint16_t width, uint16_t height, uint16_t xhot, uint16_t yhot,uint32_t serialNumber, uint32_t dataSize);
    ~X2GoCursor();
    uint16_t xhot;
    uint16_t yhot;
    uint32_t serialNumber;
    uint32_t dataSize;
    uint16_t width;
    uint16_t height;
};

class OutputChunk
{
public:
    OutputChunk(SelectionType selection, SelectionMime mimeType);
    QByteArray data;
    enum SelectionMime mimeData;
    bool firstChunk=false;
    bool lastChunk=false;
    uint32_t totalSize=0;
    enum SelectionType selection;
};

class DgramPacket
{
public :
    DgramPacket(uint16_t seq, uint16_t numOfDatagrams);
    bool isComplete(){return complete;}
    bool addDgram(QByteArray dgram);
    int getNumberOfDatagrams(){return datagrams.size();};
    QByteArray getData(){return data;};
    uint16_t getPacketSeq(){return packetSeq;};
private:
    bool complete=false;
    QVector<QByteArray> datagrams;
    QByteArray data;
    uint16_t packetSeq;
};

class DisplayArea;
class QTimer;
class MenuFrame;
class QMenu;
class QAction;
class QLabel;
class ScreenIdentifier;
class ExtWin;
class QTcpSocket;
class QUdpSocket;

class Client : public QMainWindow
{
    Q_OBJECT
public:
    Client();
    ~Client();
    void sendEvent(char *event);
    Frame* getCurrentFrame(){return currentFrame;}
    bool needUpdate(){return wantRepaint;}
    void setUptodate();
    QPixmap getPixmapFromCache(uint32_t crc);
    QPixmap addPixmapToCache();
    bool isDisplayPointer(QMouseEvent* event);
    void addToSelectionOutput(OutputChunk* chunk);
    bool serverSupportsExtSelection(){return serverExtSelection;}
    ClipboardMode clipboardMode(){return clipMode;}
    void requestSelectionFromServer(SelectionType sel);
    void send_selnotify_to_server(SelectionType selection, SelectionMime mime);
    int max_chunk();
    static QByteArray zuncompress(const char* data, uint compressed_size, uint size);
    static QTextStream& KDRStdErrFunc(bool dbg=true);
    static QString QRectToStr(const QRect& rec);
    static QString QSizeToStr(const QSizeF& sz);
    void changeWindow(ExtWin* win, uint8_t newState=UNCHANGED);
    bool isRootless(){return rootless;}
    const QList<ExtWin*> getExtWindows(){return extWindows;}
    const QList<ExtWin*> getSiblings(ExtWin* win);
    ExtWin* findExtWinById(uint32_t extWinId);
    QImage* getDisplayImage(){return &displayImage;}
    void repaintAllWindows();
    quint16 getPublicServerVersion(){return serverVersion;}

public slots:
    void sendOutputSelChunk();

private slots:
    void sendClientVersion();
    void slotIdentifyScreen();
    void connectToServer();
    void socketConnected();
    void socketDisconnected();
    void socketError(QAbstractSocket::SocketError socketError);
    void dataArrived();
    void UDPDataArrived();
    void slotSynchronize();

    void slotScreenAdded(QScreen* screen);
    void slotScreenRemoved(QScreen* screen);
    void geometryChanged();
    void resizeToOldSize();
    void resizeToFS();
    void resizeToSaved();

    void slotSetupMenu();
    void slotDisconnect();
    void slotFS();
    void slotRealFs();
    void slotRestore();
    void slotDisplayFS();
    void slotEnableRandr();
    void slotResizeFSFinal();
    void slotSelectionChanged(QClipboard::Mode mode);
    void requestCacheRebuild();
    void checkServerVersion();
    void slotCheckIfServerIsAlive();
public slots:
    void editWindowTitle();


private:
    enum{ HEADER, FRAMEREGION, REGIONDATA ,CURSORDATA, CURSORLIST, FRAMELIST, SELECTIONBUFFER, WINUPDATEBUFFER } currentDataType;
    enum HeaderType{ FRAME, DELETEDFRAMES, CURSOR, DELETEDCURSORS, SELECTION, SERVER_VERSION, DEMANDCLIENTSELECTION,
        REINIT, WINUPDATE, SRVKEEPALIVE, SRVDISCONNECT, CACHEFRAME, UDPOPEN, UDPFAILED};
    void getServerversion();
    void getClientSelection();
    void setUseRandr(bool use);
    void exitOnError(const QString& message);
    void getImageFrame();
    void getImageFrameFromDGPacket(QByteArray data);
    void readDataHeader();
    void getFrameRegion();
    void getRegionImage();
    void getCursor();
    void getCursorImage();
    void getDeletedFrames();
    void getDeletedCursors();
    void getDeletedFramesList();
    void getDeletedCursorsList();
    void getSelection();
    void getSelectionBuffer();
    void getWinUpdate();
    void getWinUpdateBuffer();
    void renderFrame();
    void requestUdpFrames();
    void openUdpConnection();
    void freeMessageBuffer();
    void setCursor();
    void sendGeometryEvent();
    void setFS(int screenNumber);
    void reinitCaches();
    void initGeometry();
    void setDisplayImage();
    void readDgram();
    void requestFrame(uint32_t crc);
    int findPacket(QList<DgramPacket*>* list, uint16_t seq);
    void updateServerAlive();
    bool wantRepaint=false;
    bool hasUpdates=false;
#ifndef Q_OS_LINUX
    void sendSelectionToServer(SelectionType selection);
    void setInputSelectionData(SelectionType selection, SelectionMime mime, bool firstChunk, bool lastChunk, uint32_t compressed, uint size, char* data, bool notify=false);
#endif
    void parseOptions();
    void initDesktopMode();
    QImage displayImage;


    static bool debug;
    //initial values
    int width=800;
    int height=600;
    QString host="localhost";
    QString udpHost="localhost";
    int port=15000;


    //feature version of server
    quint16 serverVersion=0;

    bool fullscreen=false;
    bool multidisp=false;
    int dispNumber=1;
    bool serverExtSelection=false;
    bool rootless=false;
    bool udpFrames=false;
    int udpConnectionAttempts=0;
    time_t lastServerPacketTime=0;
    bool noresize=false;
    QString cookie;

    QString mainWndTitle;

    MenuFrame *FSMenuBar;
    QMenu* menu;

    QAction* actRandr;
    QAction* actFS;
    QAction* actDisp[4];
    QAction* actRestore;
    QAction* actDisconnect;

    bool wasInRealFs=false;

    DisplayArea* displayArea=0l;
    Frame* currentFrame=0;
    X2GoCursor* currentCursor=0l;
    int currentRegionNumber=0;
    bool useRandr=false;
    bool isFs=false;
    int FSScreen=-1;
    QSize savedSize;
    QPoint savedPosition;
    Qt::WindowFlags savedFlags;

    QTcpSocket* clientSocket=0l;
    QUdpSocket* udpSocket=0l;
    int bytesLeftToRead=0;
    int bytesReady=0;
    char* messageBuffer=0l;
    bool connected=false;


    //input selection chunk variables
    //size of current chunk
    uint32_t selectionSize;
    //size of complete selection
    uint32_t selectionTotalSize;
    //format of chunk, string or pix
    SelectionMime selectionFormat;
    //if true clipboard else primary
    SelectionType selectionClipboard;
    //if it's the first chunk in multiply sel
    bool firstChunk;
    //if it's the last chunk in multiply sel
    bool lastChunk;
    //if the chunk compressed the size is > 0
    uint32_t compressed_size;
    //////////

    ClipboardMode clipMode=CLIP_BOTH; //clipboard mode: both, server, client or none

    uint32_t deletedFramesSize=0;
    uint32_t deletedCursorsSize=0;
    int winUpdateSize=0;

    QHash <uint32_t, QPixmap> frameCache;
    QHash <uint32_t, QCursor*> cursorCache;
    QList <OutputChunk*> outputSelectionQueue;
    QList <ExtWin*> extWindows;
    QList <DgramPacket*> serverFramePackets, serverRepaintPackets;
    //last succesfully processed frame packet sequence
    uint16_t serverFrameSeq=0-1;
    //last succesfully processed repaint packet sequence
    uint16_t serverRepaintSeq=0-1;

    int frameCount=0;

    QTimer* geometryDelay=0l;
    QTimer* checkSrvAliveTimer=0l;
    QRect currentGeometry;
    QRect restoreGeometry;

    QRect ephyrScreens[4];
    int primaryScreenIndex=0;
    int cacheSize=0;
    ScreenIdentifier *screenIdentifier=0l;
    QLabel* fr;

    //selection
#ifdef Q_OS_LINUX
    XCBClip* clipboard;
#else
    SelectionMime selMime; //mime of selection (string or image)
    QByteArray selData; //data
    uint32_t total_compressed=0;
#endif

protected:
    void resizeEvent(QResizeEvent*);
    void moveEvent(QMoveEvent*);
    void closeEvent(QCloseEvent* );
#ifdef Q_OS_WIN
    bool nativeEvent(const QByteArray &eventType, void *message, long *result);
#endif
};

#endif // CLIENT_H
