package xyz.capybara.clamav.commands;

import xyz.capybara.clamav.exceptions.CommunicationException;
import xyz.capybara.clamav.exceptions.UnknownCommandException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;

@Slf4j
public abstract class Command<T> {

    private static final String UNKNOWN_COMMAND = "UNKNOWN COMMAND";

    public abstract String getCommandString();

    public T send(InetSocketAddress server) {
        try (SocketChannel socketChannel = SocketChannel.open(server)) {
            socketChannel.write(getRawCommand());

            return readResponse(socketChannel);
        } catch (IOException e) {
            throw new CommunicationException(e);
        }
    }

    protected abstract CommandFormat getFormat();

    protected ByteBuffer getRawCommand() {
        StringBuilder rawCommand = new StringBuilder();
        rawCommand.append(getFormat().getPrefix())
                .append(getCommandString())
                .append(getFormat().getTerminator());

        return ByteBuffer.wrap(rawCommand.toString().getBytes(StandardCharsets.US_ASCII));
    }

    protected T readResponse(SocketChannel socketChannel) throws IOException {
        StringBuilder responseStringBuilder = new StringBuilder();
        ByteBuffer rawResponsePart = ByteBuffer.allocate(32);

        for (int read = socketChannel.read(rawResponsePart); read > -1; read = socketChannel.read(rawResponsePart)) {
            responseStringBuilder.append(new String(rawResponsePart.array(), StandardCharsets.US_ASCII));
            rawResponsePart = ByteBuffer.allocate(32);
        }

        String responseString = normalizeResponseString(responseStringBuilder.toString());

        if (responseString.equals(UNKNOWN_COMMAND)) {
            throw new UnknownCommandException(getCommandString());
        }

        log.debug("{} - Response: {}", getCommandString(), responseString);

        return parseResponse(responseString);
    }

    private String normalizeResponseString(String responseString) {
        responseString = responseString.replaceAll("\0+", "");
        if (getFormat() == CommandFormat.NEW_LINE) {
            responseString = responseString.substring(0, responseString.lastIndexOf('\n'));
        }

        return responseString;
    }

    protected abstract T parseResponse(String responseString);

    protected enum CommandFormat {
        NULL_CHAR('z', '\0'),
        NEW_LINE('n', '\n');

        @Getter
        private char prefix, terminator;

        CommandFormat(char prefix, char terminator) {
            this.prefix = prefix;
            this.terminator = terminator;
        }
    }
}
@@ -1608,37 +1608,39 @@
     
-r
+String rawR
 esponse
+Part
 String
-Builder.append(
+ = 
 new 
@@ -1693,24 +1693,166 @@
 ts.US_ASCII)
+;%0A            rawResponsePartString = rawResponsePartString.substring(0, read);%0A            responseStringBuilder.append(rawResponsePartString
 );%0A         
@@ -1940,24 +1940,21 @@
 tring = 
-normaliz
+remov
 eRespons
@@ -1954,22 +1954,26 @@
 Response
-String
+Terminator
 (respons
@@ -2286,16 +2286,13 @@
 ing 
-normaliz
+remov
 eRes
@@ -2300,64 +2300,25 @@
 onse
-String(String responseString) %7B%0A        response
+Terminator(
 String
- =
  res
@@ -2332,81 +2332,8 @@
 ring
-.replaceAll(%22%5C0+%22, %22%22);%0A        if (getFormat() == CommandFormat.NEW_LINE
 ) %7B%0A
@@ -2344,28 +2344,14 @@
     
-  
-  responseString =
+return
  res
@@ -2406,56 +2406,37 @@
 xOf(
-'%5Cn'));%0A        %7D%0A%0A        return responseString
+getFormat().getTerminator()))
 ;%0A  
@@ -2509,24 +2509,21 @@
 ;%0A%0A    p
-rotected
+ublic
  enum Co
@@ -2789,20 +2789,336 @@
 r;%0A        %7D
+%0A%0A        public static CommandFormat fromPrefix(char prefix) %7B%0A            CommandFormat format = null;%0A            switch (prefix) %7B%0A                case 'z':%0A                    format = NULL_CHAR;%0A                case 'n':%0A                    format = NEW_LINE;%0A            %7D%0A            return format;%0A        %7D
 %0A    %7D%0A%7D

Fix response reader to avoid reading too many bytes in the buffer