/*
File to be included into relevant device REST setup to implement the default common mandatory 
ASCOM interface commands. 
Note that the action commands return NOT_IMPLEMENTED. Override if this is not what you need. 
Also includes default ALPACA Discovery handlers - you will need to listen for the ALPACA UDP calls from your main loop handler.
*/
/*Change Log
01/06/2021 Updated getActions to return empty array rather than array[] with an empty string to pass Conform compliance checks - tested with dome. 
31/03/2025 Updated InterfaceVersion to return uint32 instead of String due to ASCOM strict checking introduced after release v7
*/

//Assumes Use of ARDUINO ESP8266WebServer for entry handlers
#if !defined _ASCOMAPI_Common
#define _ASCOMAPI_Common
#include "AlpacaErrorConsts.h"
#include "ArduinoJson.h"
#include "JSONHelperFunctions.h"

//#define DEBUG_ESP_HTTP_SERVER - re-used from ESP8266WebServer.h but due to a dependency copied here. 
#ifndef DEBUG_OUTPUT
#ifdef DEBUG_ESP_PORT
#define DEBUG_OUTPUT DEBUG_ESP_PORT
#else
#define DEBUG_OUTPUT Serial
#endif
#endif

/*
Required External variables - Specify these in your driver - they are driver type and instance specific. 
extern unsigned int connected;
extern String DriverName;
extern String DriverVersion;
extern String DriverInfo;
extern String Description;
extern String InterfaceVersion;
extern char[] GUID
extern unsigned int instanceNumber;
extern float instanceVersion;
extern char[] Location;
*/
//Generic response tracking id 
int serverTransID= 0;

//Used to track whic device last connected- if !NOT_CONNECTED then contains the ID of the connected client.
//clientID can be any integer from 0 up so this needs to be unsigned .. 
const unsigned int NOT_CONNECTED = (unsigned int) 0;

//PUT /{DeviceType}/{DeviceNumber}/Action Invokes the specified device-specific action.
void handleAction(void);

//PUT /{DeviceType}/{DeviceNumber}/CommandBlind Transmits an arbitrary string to the device
void handleCommandBlind(void);

//PUT /{DeviceType}/{DeviceNumber}/CommandBool Transmits an arbitrary string to the device and returns a boolean value from the device.
void handleCommandBool(void);

//PUT /{DeviceType}/{DeviceNumber}/CommandString Transmits an arbitrary string to the device and returns a string value from the device
void handleCommandString(void);

//GET /{DeviceType}/{DeviceNumber}/Connected Retrieves the connected state of the device
//PUT /{DeviceType}/{DeviceNumber}/Connected Sets the connected state of the device
void handleConnected(void);

//GET /{DeviceType}/{DeviceNumber}/Description Device description
void handleDescriptionGet(void);

//GET /{DeviceType}/{DeviceNumber}/Driverinfo Device driver description
void handleDriverInfoGet(void);

//GET /{DeviceType}/{DeviceNumber}/DriverVersion Driver Version
void handleDriverVersionGet(void);

//GET /{DeviceType}/{DeviceNumber}/InterfaceVersion Driver Version
void handleInterfaceVersionGet(void);

//GET /{DeviceType}/{DeviceNumber}/Name Device name
void handleNameGet(void);
void handleNamePut(void); //Non-ASCOM , required by setup

//GET /{DeviceType}/{DeviceNumber}/SupportedActions Returns the list of action names supported by this driver.  
void handleSupportedActionsGet(void);

bool validateCommonRequest( String& message, JsonObject& root, uint32_t& clientID, uint32_t& clientTransID, String valueVarName = "" )
{
    String clientIDValue = "";
    String clientTransIDValue = "";
    String clientIDVarName = "ClientID";
    String clientTransIDVarName = "ClientTransactionID";
    if ( hasArgIC( clientIDVarName, server, false ) )
      clientIDValue = server.arg( clientIDVarName );
    if ( hasArgIC( clientTransIDVarName, server, false ) )
      clientTransIDValue = server.arg( clientTransIDVarName );

    String value = "";
    if ( valueVarName.length() > 0 )
    {
      String matchedValueVarName = valueVarName;
      if ( hasArgIC( matchedValueVarName, server, false ) )
        value = server.arg( matchedValueVarName );
    }

    Serial.printf( "Query Info: Using method %i, received ClientID %s, transID %s, connection parameter: %s ",
                   server.method(), clientIDValue.c_str(), clientTransIDValue.c_str(), value.c_str() );

    bool validRequest = isValidRequest( clientID, "ClientID", clientTransID, "ClientTransactionID", server );
    if( !validRequest )
    {
      jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("Connected"), invalidValue, F("Invalid argument type or value") );
      root["Value"] = "0";
      root.printTo(message);
      server.send(200, F("application/json"), message);
#ifdef DEBUG_ESP_HTTP_SERVER
      DEBUG_OUTPUT.println( message );
#endif
      return false;
    }

    return true;
}

void sendCommonResponse( String& message, int retVal = 200 )
{
    server.send( retVal, F("application/json"), message);
#ifdef DEBUG_ESP_HTTP_SERVER
    DEBUG_OUTPUT.println( message );
#endif
}

void handleAction(void)
{
    String message;
    uint32_t clientID = (uint32_t)0;
    uint32_t clientTransID = (uint32_t)0;
    DynamicJsonBuffer jsonBuff(256);
    JsonObject& root = jsonBuff.createObject();

    if( !validateCommonRequest( message, root, clientID, clientTransID, "Action" ) )
      return;

    jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("Action"), notImplemented, F("Not implemented") );
    root["Value"] = "";
    root.printTo(message);
    sendCommonResponse( message );
    return;
}

void handleCommandBlind(void)
{
    String message;
    uint32_t clientID = (uint32_t)0;
    uint32_t clientTransID = (uint32_t)0;
    DynamicJsonBuffer jsonBuff(256);
    JsonObject& root = jsonBuff.createObject();

    if( !validateCommonRequest( message, root, clientID, clientTransID, "Command" ) )
      return;

    jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("CommandBlind"), notImplemented, F("Not implemented") );
    root["Value"] = "";
    root.printTo(message);
    sendCommonResponse( message );
    return;
}

void handleCommandBool(void)
{
    String message;
    uint32_t clientID = (uint32_t)0;
    uint32_t clientTransID = (uint32_t)0;
    DynamicJsonBuffer jsonBuff(256);
    JsonObject& root = jsonBuff.createObject();

    if( !validateCommonRequest( message, root, clientID, clientTransID, "Command" ) )
      return;

    jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("CommandBool"), notImplemented, F("Not implemented") );
    root["Value"] = false;
    root.printTo(message);
    sendCommonResponse( message );
    return;
}

void handleCommandString(void)
{
    String message;
    uint32_t clientID = (uint32_t)0;
    uint32_t clientTransID = (uint32_t)0;
    DynamicJsonBuffer jsonBuff(256);
    JsonObject& root = jsonBuff.createObject();

    if( !validateCommonRequest( message, root, clientID, clientTransID, "Command" ) )
      return;

    jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("CommandString"), notImplemented, F("Not implemented") );
    root["Value"] = "";
    root.printTo(message);
    sendCommonResponse( message );
    return;
}

/*
Record the clientID of the connected client in the connected variable. 
Use a value of -1 ti indicate not connected. 
TODO- record a count for connected - disconnected

*/
void handleConnected(void)
{
    String message;
    DynamicJsonBuffer jsonBuff(256);
    JsonObject& root = jsonBuff.createObject();
    uint32_t clientID = (uint32_t)0;
    uint32_t clientTransID = (uint32_t)0;
    bool tobeConnected = false;
    int retVal = 200;

    if( !validateCommonRequest( message, root, clientID, clientTransID, "Connected" ) )
      return;

    String connectedVarName = "Connected";
    bool hasConnectedArg = hasArgIC( connectedVarName, server, false );
    if ( hasConnectedArg )
    {
        String arg = server.arg( connectedVarName );
        arg.trim();
        if( arg.equalsIgnoreCase( "true" ) )
        {
            tobeConnected = true;
            Serial.printf( "connection info: existing connected Id %u, client: %u, transID %u, connection request : %s \n", connected, clientID, clientTransID, "true" );
        }
        else if ( arg.equalsIgnoreCase( "false" ) )
        {
            tobeConnected = false;
            Serial.printf( "connection info: existing connected Id %u, client: %u, transID %u, connection request : %s \n", connected, clientID, clientTransID, "false" );
        }
        else
        {
            jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("Connected"), invalidValue, F("Invalid argument type or value") );
            root["Value"] = "0";
            root.printTo(message);
            sendCommonResponse( message );
            return;
        }
    }

    if ( server.method() == HTTP_PUT )
    {
      Serial.printf( "handleConnected: new state: %d, this client ID: %i, connectedID: %i\n", tobeConnected, clientID, connected );
      if ( hasConnectedArg )
      {
         if ( tobeConnected == true )
         {
           if ( connected != NOT_CONNECTED )
           {
             jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("Connected"), notConnected, F("Another client already connected") );
             root["Value"] = false;
             retVal = 200;
           }
           else
           {
              connected = clientID;
              jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("Connected"), Success, "" );
              root["Value"] = true;
              connectionCtr++;
              retVal = 200;
           }
         }
         else
         {
           if ( connected != NOT_CONNECTED )
           {
             if( connected == clientID )
             {
   #ifdef _ASCOM_Dome
                if( parkDomeOnDisconnect )
                    addDomeCmd( 0, 0, CMD_DOME_PARK, 0);
                if( closeShutterOnDisconnect )
                    addShutterCmd( 0, 0, CMD_SHUTTER_CLOSE, 0 );
   #endif
                jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("Connected"), Success, "" );
                connected = NOT_CONNECTED;
                root["Value"] = true;
                connectionCtr--;
                retVal = 200;
             }
             else
             {
                jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("Connected"), notConnected, F("This client is not the connected client") );
                root["Value"] = false;
                retVal = 200;
             }
           }
           else
           {
             jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("Connected"), Success, "" );
             root["Value"] = true;
             connected = NOT_CONNECTED;
             retVal = 200;
           }
         }
       }
       else
       {
         jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("Connected"), invalidOperation , F("Unable to parse 'Connected' value  during PUT") );
         retVal = 200;
       }
    }
    else if ( server.method() == HTTP_GET )
    {
      jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("Connected"), Success, "" );
      root["Value"] = (connected != NOT_CONNECTED);
      retVal = 200;
    }
    else
    {
      jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("Connected"), invalidOperation , F( "Unexpected HTTP method" ) );
      root["Value"] = connected;
      retVal = 400;
    }

   root.printTo(message);
   sendCommonResponse( message, retVal );
   return;
}

void handleDescriptionGet(void)
{
    String message;
    uint32_t clientID = (uint32_t)0;
    uint32_t clientTransID = (uint32_t)0;
    DynamicJsonBuffer jsonBuff(256);
    JsonObject& root = jsonBuff.createObject();

    if( !validateCommonRequest( message, root, clientID, clientTransID ) )
      return;

    jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("Description"), 0, "" );
    root["Value"] = String(Description);
    root.printTo(message);
    sendCommonResponse( message );
    return ;
}

void handleDriverInfoGet(void)
{
    String message;
    uint32_t clientID = (uint32_t)0;
    uint32_t clientTransID = (uint32_t)0;
    DynamicJsonBuffer jsonBuff(256);
    JsonObject& root = jsonBuff.createObject();

    if( !validateCommonRequest( message, root, clientID, clientTransID ) )
      return;

    jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("DriverInfo"), Success, "" );
    root["Value"] = String(DriverInfo);
    root.printTo(message);
    sendCommonResponse( message );
    return ;
}

void handleDriverVersionGet(void)
{
    String message;
    uint32_t clientID = (uint32_t)0;
    uint32_t clientTransID = (uint32_t)0;
    DynamicJsonBuffer jsonBuff(256);
    JsonObject& root = jsonBuff.createObject();

    if( !validateCommonRequest( message, root, clientID, clientTransID ) )
      return;

    jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("DriverVersion"), Success, "" );
    root["Value"] = (String)(DriverVersion);
    root.printTo(message);
    sendCommonResponse( message );
    return ;
}

//Updated after ASCOM V7 release strictly requires this to be a uint32
void handleInterfaceVersionGet(void)
{
    String message;
    uint32_t clientID = (uint32_t)0;
    uint32_t clientTransID = (uint32_t)0;
    DynamicJsonBuffer jsonBuff(256);
    JsonObject& root = jsonBuff.createObject();

    if( !validateCommonRequest( message, root, clientID, clientTransID ) )
      return;

    jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("InterfaceVersion"), Success, "" );
    root["Value"] = (int32) InterfaceVersion;
    root.printTo(message);
    sendCommonResponse( message );
    return ;
}

void handleNameGet(void)
{
    String message;
    uint32_t clientID = (uint32_t)0;
    uint32_t clientTransID = (uint32_t)0;
    DynamicJsonBuffer jsonBuff(256);
    JsonObject& root = jsonBuff.createObject();

    if( !validateCommonRequest( message, root, clientID, clientTransID ) )
      return;

    jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("Name"), Success, "" );
    root["Value"] = String( DriverName );
    root.printTo(message);
    sendCommonResponse( message );
    return ;
}

void handleSupportedActionsGet(void)
{
    String message;
    uint32_t clientID = (uint32_t)0;
    uint32_t clientTransID = (uint32_t)0;
    DynamicJsonBuffer jsonBuff(256);
    JsonObject& root = jsonBuff.createObject();

    if( !validateCommonRequest( message, root, clientID, clientTransID ) )
      return;

    jsonResponseBuilder( root, clientID, clientTransID, ++serverTransID, F("SupportedActions"), Success, "" );
    root.createNestedArray("Value");
    root.printTo(message);
    sendCommonResponse( message );
    return ;
}

#endif