#include <iostream>
#include <string>
#include <vector>
#include <cstring>
#include <ctime>
#include <thread>
#include <chrono>
#include <iomanip>
#include <algorithm> // Required for std::reverse
#include <fstream>   // For saving data to CSV

#ifdef _WIN32
    #include <winsock2.h>
    #include <ws2tcpip.h>
    #pragma comment(lib, "ws2_32.lib")
    typedef int socklen_t;
#else
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #include <unistd.h>
    typedef int SOCKET;
    #define INVALID_SOCKET -1
    #define SOCKET_ERROR -1
    #define closesocket close
#endif

// Force 1-byte packing to match Sierra Chart's expectations
#pragma pack(push, 1)

// DTC Protocol Message Types
enum DTCMessageType {
    LOGON_REQUEST = 1,
    LOGON_RESPONSE = 2,
    HEARTBEAT = 3,
    ENCODING_REQUEST = 6,
    ENCODING_RESPONSE = 7,
    MARKET_DATA_REQUEST = 101,
    MARKET_DATA_REJECT = 103,
    MARKET_DATA_SNAPSHOT = 104,
    MARKET_DATA_UPDATE_TRADE = 107,
    MARKET_DATA_UPDATE_BID_ASK = 108,
    HISTORICAL_PRICE_DATA_REQUEST = 800,
    HISTORICAL_PRICE_DATA_RESPONSE_HEADER = 801,
    HISTORICAL_PRICE_DATA_REJECT = 802,
    HISTORICAL_PRICE_DATA_RECORD_RESPONSE = 803
};

// DTC Protocol Encoding Types
enum DTCEncoding {
    BINARY_ENCODING = 0
};

// Historical data interval types
enum HistoricalDataIntervalType {
    TICK_DATA = 0,
    ONE_MINUTE = 1,
    FIVE_MINUTES = 2,
    DAILY = 3,
    WEEKLY = 4,
    MONTHLY = 5
};

// Message structures with explicit byte sizes
struct EncodingRequest {
    uint16_t Size;
    uint16_t Type;
    int32_t Encoding;
    char ProtocolVersion[32];
};

struct LogonRequest {
    uint16_t Size;
    uint16_t Type;
    char Username[32];
    char Password[32];
    char GeneralTextData[64];
    int32_t Integer_1;
    int32_t Integer_2;
    int32_t HeartbeatIntervalInSeconds;
    char TradeAccount[32];
    char HardwareIdentifier[64];
    char ClientName[32];
};

struct LogonResponse {
    uint16_t Size;
    uint16_t Type;
    int32_t Result;
    char ResultText[64];
    char ReconnectAddress[64];
    int32_t Integer_1;
    char ServerName[60];
};

struct HeartbeatMessage {
    uint16_t Size;
    uint16_t Type;
    int32_t Integer_1;
};

struct HistoricalPriceDataRequest {
    uint16_t Size;
    uint16_t Type;
    int32_t RequestID;
    char Symbol[64];
    char Exchange[16];
    int32_t RecordInterval;
    int32_t StartDateTime;
    int32_t EndDateTime;
    uint8_t UseZLibCompression;
    uint8_t RequestDividendAdjustedStockData;
    int32_t Integer_1;
};

struct HistoricalPriceDataResponseHeader {
    uint16_t Size;
    uint16_t Type;
    int32_t RequestID;
    int32_t RecordInterval;
    uint8_t UseZLibCompression;
    char Symbol[64];
    char Exchange[16];
};

struct HistoricalPriceDataReject {
    uint16_t Size;
    uint16_t Type;
    int32_t RequestID;
    char RejectText[64];
};

struct HistoricalPriceDataRecordResponse {
    uint16_t Size;
    uint16_t Type;
    int32_t RequestID;
    float StartDateTime;
    float OpenPrice;
    float HighPrice;
    float LowPrice;
    float LastPrice;
    float Volume;
    uint32_t OpenInterest;
    float BidVolume;
    float AskVolume;
    uint8_t IsFinalRecord;
};

#pragma pack(pop)

// Debug utility to print hex dump of data
void hexDump(const char* desc, const void* addr, const int len) {
    int i;
    const unsigned char* pc = static_cast<const unsigned char*>(addr);
    
    if (desc != nullptr) {
        printf("%s [%d bytes]:\n", desc, len);
    }
    
    for (i = 0; i < len; i++) {
        if (i % 16 == 0) {
            printf("%04x: ", i);
        }
        
        printf("%02x ", pc[i]);
        
        if ((i + 1) % 16 == 0) {
            printf("\n");
        }
    }
    
    if (i % 16 != 0) {
        printf("\n");
    }
}

// Time conversion utilities
time_t convertToEpochTime(int year, int month, int day) {
    struct tm timeinfo = {0};
    timeinfo.tm_year = year - 1900;
    timeinfo.tm_mon = month - 1;
    timeinfo.tm_mday = day;
    timeinfo.tm_hour = 0;
    timeinfo.tm_min = 0;
    timeinfo.tm_sec = 0;
    
    #ifdef _WIN32
        return _mkgmtime(&timeinfo);
    #else
        return timegm(&timeinfo);
    #endif
}

// Format timestamp to human-readable date
std::string formatDate(float timestamp) {
    time_t time = static_cast<time_t>(timestamp);
    struct tm timeinfo;
    
    #ifdef _WIN32
        gmtime_s(&timeinfo, &time);
    #else
        gmtime_r(&time, &timeinfo);
    #endif
    
    char buffer[80];
    strftime(buffer, sizeof(buffer), "%Y-%m-%d", &timeinfo);
    return std::string(buffer);
}

// DTC Protocol Client class
class SierraChartClient {
private:
    SOCKET socket_fd;
    std::string host;
    int port;
    std::string username;
    std::string password;
    std::string symbol;
    bool connected;
    bool historical_mode;
    int data_interval;
    int days_of_data;
    std::thread heartbeat_thread;
    bool heartbeat_running;
    std::vector<HistoricalPriceDataRecordResponse> historical_data;
    
    // Initialize socket connection
    bool initializeSocket() {
        #ifdef _WIN32
            WSADATA wsaData;
            if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
                std::cerr << "Failed to initialize Winsock" << std::endl;
                return false;
            }
        #endif
        
        socket_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (socket_fd == INVALID_SOCKET) {
            std::cerr << "Failed to create socket" << std::endl;
            return false;
        }
        
        struct sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(port);
        
        if (inet_pton(AF_INET, host.c_str(), &server_addr.sin_addr) <= 0) {
            std::cerr << "Invalid address or address not supported" << std::endl;
            closesocket(socket_fd);
            return false;
        }
        
        if (::connect(socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            std::cerr << "Connection failed" << std::endl;
            closesocket(socket_fd);
            return false;
        }
        
        connected = true;
        std::cout << "Connected to " << host << ":" << port << std::endl;
        return true;
    }

    // Send a message with optional hex dump
    bool sendMessage(const void* buffer, size_t length, bool debug = true) {
        if (debug) {
            hexDump("SENDING", buffer, length);
        }
        
        int bytes_sent = send(socket_fd, static_cast<const char*>(buffer), length, 0);
        if (bytes_sent == SOCKET_ERROR) {
            std::cerr << "Failed to send message" << std::endl;
            return false;
        }
        
        if (debug) {
            std::cout << "Sent " << bytes_sent << " bytes" << std::endl;
        }
        return true;
    }
    
    // Receive a message with optional hex dump
    int receiveMessage(void* buffer, size_t max_length, bool debug = true) {
        int bytes_received = recv(socket_fd, static_cast<char*>(buffer), max_length, 0);
        if (bytes_received == SOCKET_ERROR) {
            std::cerr << "Failed to receive message" << std::endl;
            return -1;
        }
        
        if (bytes_received > 0 && debug) {
            hexDump("RECEIVED", buffer, bytes_received);
        }
        
        return bytes_received;
    }
    
    // Send encoding request
    bool sendEncodingRequest() {
        EncodingRequest request;
        memset(&request, 0, sizeof(request));
        request.Size = sizeof(request);
        request.Type = ENCODING_REQUEST;
        request.Encoding = BINARY_ENCODING;
        strcpy(request.ProtocolVersion, "DTC1.5");
        
        std::cout << "Sending encoding request..." << std::endl;
        return sendMessage(&request, sizeof(request));
    }
    
    // Send logon request with proper username handling (reversed)
    bool sendLogonRequest() {
        LogonRequest request;
        memset(&request, 0, sizeof(request));
        request.Size = sizeof(request);
        request.Type = LOGON_REQUEST;
        
        // Username - exactly 32 bytes, null-padded, and pre-reversed
        memset(request.Username, 0, sizeof(request.Username)); // Ensure all zeros first
        if (!username.empty()) {
            // Create a reversed copy of the username
            std::string reversed_username = username;
            std::reverse(reversed_username.begin(), reversed_username.end());
            
            // Copy the reversed string into the request
            strncpy(request.Username, reversed_username.c_str(), sizeof(request.Username) - 1);
            request.Username[sizeof(request.Username) - 1] = '\0'; // Ensure null termination
            
            std::cout << "Original username: '" << username << "'" << std::endl;
            std::cout << "Reversed username: '" << reversed_username << "'" << std::endl;
        }
        
        // Password - exactly 32 bytes, null-padded
        memset(request.Password, 0, sizeof(request.Password)); // Ensure all zeros first
        if (!password.empty()) {
            strncpy(request.Password, password.c_str(), sizeof(request.Password) - 1);
            request.Password[sizeof(request.Password) - 1] = '\0'; // Ensure null termination
        }
        
        // Set client name
        strncpy(request.ClientName, "Sierra Chart DTC Client", sizeof(request.ClientName) - 1);
        
        // Set heartbeat interval (in seconds)
        request.HeartbeatIntervalInSeconds = 5;
        
        std::cout << "Sending logon request..." << std::endl;
        return sendMessage(&request, sizeof(request));
    }
    
    // Send a heartbeat message
    bool sendHeartbeat() {
        HeartbeatMessage heartbeat;
        memset(&heartbeat, 0, sizeof(heartbeat));
        heartbeat.Size = sizeof(heartbeat);
        heartbeat.Type = HEARTBEAT;
        heartbeat.Integer_1 = static_cast<int32_t>(time(nullptr));
        
        return sendMessage(&heartbeat, sizeof(heartbeat), false); // No debug dump for heartbeats
    }
    
    // Send historical data request
    bool sendHistoricalDataRequest() {
        HistoricalPriceDataRequest request;
        memset(&request, 0, sizeof(request));
        request.Size = sizeof(request);
        request.Type = HISTORICAL_PRICE_DATA_REQUEST;
        request.RequestID = 1;
        
        // Symbol - exactly 64 bytes, null-padded
        strncpy(request.Symbol, symbol.c_str(), sizeof(request.Symbol) - 1);
        request.Symbol[sizeof(request.Symbol) - 1] = '\0';
        
        // Exchange - exactly 16 bytes, null-padded
        strncpy(request.Exchange, "", sizeof(request.Exchange) - 1);
        request.Exchange[sizeof(request.Exchange) - 1] = '\0';
        
        // Record interval (e.g., DAILY = 3)
        request.RecordInterval = data_interval;
        
        // Calculate start and end dates
        time_t end_time = time(nullptr);
        time_t start_time = end_time - (days_of_data * 24 * 60 * 60);
        
        request.StartDateTime = static_cast<int32_t>(start_time);
        request.EndDateTime = static_cast<int32_t>(end_time);
        
        request.UseZLibCompression = 0; // No compression
        request.RequestDividendAdjustedStockData = 0; // No dividend adjustment
        
        std::cout << "Sending historical data request for symbol: " << symbol << std::endl;
        std::cout << "Interval: " << data_interval << ", Days: " << days_of_data << std::endl;
        return sendMessage(&request, sizeof(request));
    }
    
    // Process received messages
    bool processMessages() {
        char buffer[4096];
        bool keep_running = true;
        
        while (keep_running && connected) {
            int bytes_received = receiveMessage(buffer, sizeof(buffer));
            if (bytes_received <= 0) {
                std::cerr << "Connection closed or error occurred" << std::endl;
                return false;
            }
            
            // Process the message based on the type
            uint16_t* type_ptr = reinterpret_cast<uint16_t*>(buffer + 2);
            uint16_t message_type = *type_ptr;
            
            switch (message_type) {
                case ENCODING_RESPONSE:
                    std::cout << "Received encoding response" << std::endl;
                    sendLogonRequest();
                    break;
                
                case LOGON_RESPONSE: {
                    LogonResponse* response = reinterpret_cast<LogonResponse*>(buffer);
                    std::cout << "Received logon response: " << response->ResultText << std::endl;
                    
                    if (response->Result == 1) {
                        std::cout << "Logon successful" << std::endl;
                        
                        // Start heartbeat thread
                        startHeartbeatThread();
                        
                        // If in historical mode, send historical data request
                        if (historical_mode) {
                            sendHistoricalDataRequest();
                        }
                    } else {
                        std::cerr << "Logon failed: " << response->ResultText << std::endl;
                        keep_running = false;
                    }
                    break;
                }
                
                case HEARTBEAT:
                    // Respond to heartbeat with our own
                    sendHeartbeat();
                    break;
                
                case HISTORICAL_PRICE_DATA_RESPONSE_HEADER: {
                    HistoricalPriceDataResponseHeader* header = reinterpret_cast<HistoricalPriceDataResponseHeader*>(buffer);
                    std::cout << "Received historical data header for symbol: " << header->Symbol << std::endl;
                    std::cout << "Record interval: " << header->RecordInterval << std::endl;
                    break;
                }
                
                case HISTORICAL_PRICE_DATA_REJECT: {
                    HistoricalPriceDataReject* reject = reinterpret_cast<HistoricalPriceDataReject*>(buffer);
                    std::cerr << "Historical data request rejected: " << reject->RejectText << std::endl;
                    keep_running = false;
                    break;
                }
                
                case HISTORICAL_PRICE_DATA_RECORD_RESPONSE: {
                    HistoricalPriceDataRecordResponse* record = reinterpret_cast<HistoricalPriceDataRecordResponse*>(buffer);
                    
                    // Store the record
                    historical_data.push_back(*record);
                    
                    // Print record details
                    std::string date = formatDate(record->StartDateTime);
                    std::cout << "Record: " << date 
                              << " O:" << record->OpenPrice 
                              << " H:" << record->HighPrice 
                              << " L:" << record->LowPrice 
                              << " C:" << record->LastPrice 
                              << " V:" << record->Volume << std::endl;
                    
                    // If this is the final record, we can stop
                    if (record->IsFinalRecord) {
                        std::cout << "Received final historical data record" << std::endl;
                        
                        // Save data to CSV
                        saveHistoricalDataToCsv("historical_data.csv");
                        
                        keep_running = false;
                    }
                    break;
                }
                
                default:
                    std::cout << "Received unknown message type: " << message_type << std::endl;
                    break;
            }
        }
        
        return true;
    }
    
    // Save historical data to CSV file
    void saveHistoricalDataToCsv(const std::string& filename) {
        if (historical_data.empty()) {
            std::cerr << "No historical data to save" << std::endl;
            return;
        }
        
        std::ofstream file(filename);
        if (!file.is_open()) {
            std::cerr << "Failed to open file for writing: " << filename << std::endl;
            return;
        }
        
        // Write header
        file << "Date,Open,High,Low,Close,Volume,OpenInterest" << std::endl;
        
        // Write data
        for (const auto& record : historical_data) {
            std::string date = formatDate(record.StartDateTime);
            file << date << ","
                 << record.OpenPrice << ","
                 << record.HighPrice << ","
                 << record.LowPrice << ","
                 << record.LastPrice << ","
                 << record.Volume << ","
                 << record.OpenInterest << std::endl;
        }
        
        file.close();
        std::cout << "Historical data saved to " << filename << std::endl;
    }
    
    // Heartbeat thread function
    void heartbeatThreadFunc() {
        while (heartbeat_running && connected) {
            sendHeartbeat();
            std::this_thread::sleep_for(std::chrono::seconds(5));
        }
    }
    
    // Start the heartbeat thread
    void startHeartbeatThread() {
        heartbeat_running = true;
        heartbeat_thread = std::thread(&SierraChartClient::heartbeatThreadFunc, this);
    }
    
    // Stop the heartbeat thread
    void stopHeartbeatThread() {
        heartbeat_running = false;
        if (heartbeat_thread.joinable()) {
            heartbeat_thread.join();
        }
    }
    
public:
    SierraChartClient(const std::string& host, int port, const std::string& username, 
                      const std::string& password, const std::string& symbol,
                      bool historical_mode = true, int data_interval = DAILY,
                      int days_of_data = 10)
        : host(host), port(port), username(username), password(password),
          symbol(symbol), connected(false), historical_mode(historical_mode),
          data_interval(data_interval), days_of_data(days_of_data),
          heartbeat_running(false) {}
    
    ~SierraChartClient() {
        disconnect();
        
        #ifdef _WIN32
            WSACleanup();
        #endif
    }
    
    // Connect to Sierra Chart
    bool connect() {
        if (!initializeSocket()) {
            return false;
        }
        
        // Send encoding request
        if (!sendEncodingRequest()) {
            disconnect();
            return false;
        }
        
        // Small delay to allow encoding response
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        
        // Process messages
        processMessages();
        
        return true;
    }
    
    // Disconnect from Sierra Chart
    void disconnect() {
        stopHeartbeatThread();
        
        if (connected) {
            closesocket(socket_fd);
            connected = false;
            std::cout << "Disconnected from server" << std::endl;
        }
    }
};

// Parse command line arguments
void parseCommandLine(int argc, char* argv[], std::string& host, int& port,
                      std::string& username, std::string& password,
                      std::string& symbol, bool& historical_mode,
                      int& data_interval, int& days_of_data) {
    for (int i = 1; i < argc; i++) {
        std::string arg = argv[i];
        if (arg == "--host" && i + 1 < argc) {
            host = argv[++i];
        } else if (arg == "--port" && i + 1 < argc) {
            port = std::stoi(argv[++i]);
        } else if (arg == "--username" && i + 1 < argc) {
            username = argv[++i];
        } else if (arg == "--password" && i + 1 < argc) {
            password = argv[++i];
        } else if (arg == "--symbol" && i + 1 < argc) {
            symbol = argv[++i];
        } else if (arg == "--hist") {
            historical_mode = true;
        } else if (arg == "--market") {
            historical_mode = false;
            port = 11099; // Market data port
        } else if (arg == "--interval" && i + 1 < argc) {
            data_interval = std::stoi(argv[++i]);
        } else if (arg == "--days" && i + 1 < argc) {
            days_of_data = std::stoi(argv[++i]);
        }
    }
}

int main(int argc, char* argv[]) {
    // Default parameters
    std::string host = "127.0.0.1";
    int port = 11098; // Historical data port by default
    std::string username = "colin0021";
    std::string password = "Genius5567$";
    std::string symbol = "ES";
    bool historical_mode = true;
    int data_interval = DAILY;
    int days_of_data = 10;
    
    // Parse command line arguments
    parseCommandLine(argc, argv, host, port, username, password,
                    symbol, historical_mode, data_interval, days_of_data);
    
    std::cout << "Sierra Chart DTC Client" << std::endl;
    std::cout << "Host: " << host << ":" << port << std::endl;
    std::cout << "Username: " << username << std::endl;
    std::cout << "Symbol: " << symbol << std::endl;
    std::cout << "Mode: " << (historical_mode ? "Historical" : "Market") << std::endl;
    
    // Create and run client
    SierraChartClient client(host, port, username, password, symbol,
                            historical_mode, data_interval, days_of_data);
    
    bool result = client.connect();
    
    if (!result) {
        std::cerr << "Failed to connect or retrieve data" << std::endl;
        return 1;
    }
    
    std::cout << "Client completed successfully" << std::endl;
    return 0;
}