import std/paths
import strutils, sequtils, times
import ../../common/[types, sequence, crypto, utils, serialize]

proc parseInput*(input: string): seq[string] = 
    var i = 0
    while i < input.len:

        # Skip whitespaces/tabs
        while i < input.len and input[i] in {' ', '\t'}: 
            inc i
        if i >= input.len: 
            break
        
        var arg = ""
        if input[i] == '"':
            # Parse quoted argument
            inc i # Skip opening quote

            # Add parsed argument when quotation is closed
            while i < input.len and input[i] != '"': 
                arg.add(input[i]) 
                inc i
            
            if i < input.len: 
                inc i # Skip closing quote
        
        else:
            while i < input.len and input[i] notin {' ', '\t'}: 
                arg.add(input[i])
                inc i
        
        # Add argument to returned result
        if arg.len > 0: result.add(arg)

proc parseArgument*(argument: Argument, value: string): TaskArg = 
    
    var arg: TaskArg
    arg.argType = cast[uint8](argument.argumentType)  

    case argument.argumentType:

    of INT: 
        # Length: 4 bytes        
        let intValue = cast[uint32](parseUInt(value))
        arg.data = @[byte(intValue and 0xFF), byte((intValue shr 8) and 0xFF), byte((intValue shr 16) and 0xFF), byte((intValue shr 24) and 0xFF)]

    of SHORT: 
        # Length: 2 bytes 
        let shortValue = cast[uint16](parseUint(value))
        arg.data = @[byte(shortValue and 0xFF), byte((shortValue shr 8) and 0xFF)]

    of LONG: 
        # Length: 8 bytes
        var data = newSeq[byte](8)
        let longValue = cast[uint64](parseUInt(value))
        for i in 0..7:
            data[i] = byte((longValue shr (i * 8)) and 0xFF)
        arg.data = data

    of BOOL: 
        # Length: 1 byte
        if value == "true": 
            arg.data = @[1'u8] 
        elif value == "false": 
            arg.data = @[0'u8] 
        else: 
            raise newException(ValueError, "Invalid value for boolean argument.")

    of STRING:
        arg.data = string.toBytes(value)

    of BINARY: 
        # A binary data argument consists of the file name (without the path) and the file content in bytes, both prefixed with their length as a uint32
        var packer = Packer.init() 

        let fileName = cast[string](extractFilename(cast[Path](value)))
        packer.addDataWithLengthPrefix(string.toBytes(fileName))
        
        let fileContents = readFile(value)
        packer.addDataWithLengthPrefix(string.toBytes(fileContents))

        arg.data = packer.pack() 
    
    return arg

proc createTask*(agentId, listenerId: string, command: Command, arguments: seq[string]): Task = 

    # Construct the task payload prefix
    var task: Task
    task.taskId = string.toUuid(generateUUID()) 
    task.listenerId = string.toUuid(listenerId)
    task.timestamp = uint32(now().toTime().toUnix())
    task.command = cast[uint16](command.commandType) 
    task.argCount = uint8(arguments.len)

    var taskArgs: seq[TaskArg]

    # Add the task arguments 
    if arguments.len() < command.arguments.filterIt(it.isRequired).len(): 
        raise newException(CatchableError, "Missing required argument.")

    for i, arg in arguments: 
        if i < command.arguments.len():
            taskArgs.add(parseArgument(command.arguments[i], arg))  
        else:
            # Optional arguments should ALWAYS be placed at the end of the command and take the same definition
            taskArgs.add(parseArgument(command.arguments[^1], arg))  

    task.args = taskArgs   

    # Construct the header
    var taskHeader: Header
    taskHeader.magic = MAGIC
    taskHeader.version = VERSION 
    taskHeader.packetType = cast[uint8](MSG_TASK)
    taskHeader.flags = cast[uint16](FLAG_ENCRYPTED)
    taskHeader.size = 0'u32
    taskHeader.agentId = string.toUuid(agentId)
    taskHeader.seqNr = nextSequence(taskHeader.agentId)
    taskHeader.iv = generateBytes(Iv) # Generate a random IV for AES-256 GCM
    taskHeader.gmac = default(AuthenticationTag)

    task.header = taskHeader

    # Return the task object for serialization
    return task
