/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
 
package org.apache.plc4x.java.spi.generation;
 
import io.vavr.control.Either;
import org.apache.commons.lang3.StringUtils;
import org.apache.plc4x.java.spi.utils.Serializable;
import org.apache.plc4x.java.spi.utils.ascii.AsciiBox;
import org.apache.plc4x.java.spi.utils.ascii.AsciiBoxWriter;
import org.apache.plc4x.java.spi.utils.hex.Hex;
 
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collections;
import java.util.LinkedList;
import java.util.Deque;
 
public class WriteBufferBoxBased implements WriteBuffer, BufferCommons {
 
    private final Deque<Either<AsciiBox, Deque<AsciiBox>>> boxes = new LinkedList<>();
    private final AsciiBoxWriter asciiBoxWriter;
    private final AsciiBoxWriter asciiBoxWriterLight;
    private final int desiredWidth = 120;
    private final boolean mergeSingleBoxes;
    private final boolean omitEmptyBoxes;
    private int currentWidth = desiredWidth - 2;
 
    private int pos = 1;
 
    public WriteBufferBoxBased() {
        this(false, false);
    }
 
    public WriteBufferBoxBased(boolean mergeSingleBoxes, boolean omitEmptyBoxes) {
        this(AsciiBoxWriter.DEFAULT, AsciiBoxWriter.LIGHT, mergeSingleBoxes, omitEmptyBoxes);
    }
 
 
    private WriteBufferBoxBased(AsciiBoxWriter asciiBoxWriter, AsciiBoxWriter asciiBoxWriterLight, boolean mergeSingleBoxes, boolean omitEmptyBoxes) {
        this.asciiBoxWriter = asciiBoxWriter;
        this.asciiBoxWriterLight = asciiBoxWriterLight;
        this.mergeSingleBoxes = mergeSingleBoxes;
        this.omitEmptyBoxes = omitEmptyBoxes;
    }
 
    @Override
    public ByteOrder getByteOrder() {
        // NO OP
        return ByteOrder.BIG_ENDIAN;
    }
 
    @Override
    public void setByteOrder(ByteOrder byteOrder) {
        // NO OP
    }
 
    @Override
    public int getPos() {
        return pos / 8;
    }
 
    @Override
    public void pushContext(String logicalName, WithWriterArgs... writerArgs) {
        currentWidth -= Hex.boxLineOverheat;
        boxes.offerLast(Either.right(new LinkedList<>()));
    }
 
    @Override
    public void writeBit(String logicalName, boolean value, WithWriterArgs... writerArgs) throws SerializationException {
        String additionalStringRepresentation = extractAdditionalStringRepresentation(writerArgs).map(s -> " " + s).orElse("");
        boxes.offerLast(Either.left(asciiBoxWriter.boxString(logicalName, String.format("b%d %b%s", value ? 1 : 0, value, additionalStringRepresentation), 0)));
        move(1);
    }
 
    @Override
    public void writeByte(String logicalName, byte value, WithWriterArgs... writerArgs) throws SerializationException {
        String additionalStringRepresentation = extractAdditionalStringRepresentation(writerArgs).map(s -> " " + s).orElse("");
        boxes.offerLast(Either.left(asciiBoxWriter.boxString(logicalName, String.format("0x%02x '%c'%s", value, value < 32 || value > 126 ? '.' : value, additionalStringRepresentation), 0)));
        move(8);
    }
 
    @Override
    public void writeByteArray(String logicalName, byte[] bytes, WithWriterArgs... writerArgs) throws SerializationException {
        String additionalStringRepresentation = extractAdditionalStringRepresentation(writerArgs).map(s -> " " + s).orElse("");
        if (StringUtils.isNotBlank(additionalStringRepresentation)) {
            additionalStringRepresentation += "\n";
        }
        boxes.offerLast(Either.left(asciiBoxWriter.boxString(logicalName, String.format("%s%s", Hex.dump(bytes), additionalStringRepresentation), 0)));
        move(8 * bytes.length);
    }
 
    @Override
    public void writeUnsignedByte(String logicalName, int bitLength, byte value, WithWriterArgs... writerArgs) throws SerializationException {
        String additionalStringRepresentation = extractAdditionalStringRepresentation(writerArgs).map(s -> " " + s).orElse("");
        boxes.offerLast(Either.left(asciiBoxWriter.boxString(logicalName, String.format("0x%0" + Math.max(bitLength / 4, 1) + "x %d%s", value, value, additionalStringRepresentation), 0)));
        move(bitLength);
    }
 
    @Override
    public void writeUnsignedShort(String logicalName, int bitLength, short value, WithWriterArgs... writerArgs) throws SerializationException {
        String additionalStringRepresentation = extractAdditionalStringRepresentation(writerArgs).map(s -> " " + s).orElse("");
        boxes.offerLast(Either.left(asciiBoxWriter.boxString(logicalName, String.format("0x%0" + Math.max(bitLength / 4, 1) + "x %d%s", value, value, additionalStringRepresentation), 0)));
        move(bitLength);
    }
 
    @Override
    public void writeUnsignedInt(String logicalName, int bitLength, int value, WithWriterArgs... writerArgs) throws SerializationException {
        String additionalStringRepresentation = extractAdditionalStringRepresentation(writerArgs).map(s -> " " + s).orElse("");
        boxes.offerLast(Either.left(asciiBoxWriter.boxString(logicalName, String.format("0x%0" + Math.max(bitLength / 4, 1) + "x %d%s", value, value, additionalStringRepresentation), 0)));
        move(bitLength);
    }
 
    @Override
    public void writeUnsignedLong(String logicalName, int bitLength, long value, WithWriterArgs... writerArgs) throws SerializationException {
        String additionalStringRepresentation = extractAdditionalStringRepresentation(writerArgs).map(s -> " " + s).orElse("");
        boxes.offerLast(Either.left(asciiBoxWriter.boxString(logicalName, String.format("0x%0" + Math.max(bitLength / 4, 1) + "x %d%s", value, value, additionalStringRepresentation), 0)));
        move(bitLength);
    }
 
    @Override
    public void writeUnsignedBigInteger(String logicalName, int bitLength, BigInteger value, WithWriterArgs... writerArgs) throws SerializationException {
        String additionalStringRepresentation = extractAdditionalStringRepresentation(writerArgs).map(s -> " " + s).orElse("");
        boxes.offerLast(Either.left(asciiBoxWriter.boxString(logicalName, String.format("0x%0" + Math.max(bitLength / 4, 1) + "x %d%s", value, value, additionalStringRepresentation), 0)));
        move(bitLength);
    }
 
    @Override
    public void writeSignedByte(String logicalName, int bitLength, byte value, WithWriterArgs... writerArgs) throws SerializationException {
        String additionalStringRepresentation = extractAdditionalStringRepresentation(writerArgs).map(s -> " " + s).orElse("");
        boxes.offerLast(Either.left(asciiBoxWriter.boxString(logicalName, String.format("0x%0" + Math.max(bitLength / 4, 1) + "x %d%s", value, value, additionalStringRepresentation), 0)));
        move(bitLength);
    }
 
    @Override
    public void writeShort(String logicalName, int bitLength, short value, WithWriterArgs... writerArgs) throws SerializationException {
        String additionalStringRepresentation = extractAdditionalStringRepresentation(writerArgs).map(s -> " " + s).orElse("");
        boxes.offerLast(Either.left(asciiBoxWriter.boxString(logicalName, String.format("0x%0" + Math.max(bitLength / 4, 1) + "x %d%s", value, value, additionalStringRepresentation), 0)));
        move(bitLength);
    }
 
    @Override
    public void writeInt(String logicalName, int bitLength, int value, WithWriterArgs... writerArgs) throws SerializationException {
        String additionalStringRepresentation = extractAdditionalStringRepresentation(writerArgs).map(s -> " " + s).orElse("");
        boxes.offerLast(Either.left(asciiBoxWriter.boxString(logicalName, String.format("0x%0" + Math.max(bitLength / 4, 1) + "x %d%s", value, value, additionalStringRepresentation), 0)));
        move(bitLength);
    }
 
    @Override
    public void writeLong(String logicalName, int bitLength, long value, WithWriterArgs... writerArgs) throws SerializationException {
        String additionalStringRepresentation = extractAdditionalStringRepresentation(writerArgs).map(s -> " " + s).orElse("");
        boxes.offerLast(Either.left(asciiBoxWriter.boxString(logicalName, String.format("0x%0" + Math.max(bitLength / 4, 1) + "x %d%s", value, value, additionalStringRepresentation), 0)));
        move(bitLength);
    }
 
    @Override
    public void writeBigInteger(String logicalName, int bitLength, BigInteger value, WithWriterArgs... writerArgs) throws SerializationException {
        String additionalStringRepresentation = extractAdditionalStringRepresentation(writerArgs).map(s -> " " + s).orElse("");
        boxes.offerLast(Either.left(asciiBoxWriter.boxString(logicalName, String.format("0x%0" + Math.max(bitLength / 4, 1) + "x %d%s", value, value, additionalStringRepresentation), 0)));
        move(bitLength);
    }
 
    @Override
    public void writeFloat(String logicalName, int bitLength, float value, WithWriterArgs... writerArgs) throws SerializationException {
        String additionalStringRepresentation = extractAdditionalStringRepresentation(writerArgs).map(s -> " " + s).orElse("");
        boxes.offerLast(Either.left(asciiBoxWriter.boxString(logicalName, String.format("0x%0" + Math.max(bitLength / 4, 1) + "x %f%s", Float.valueOf(value).longValue(), value, additionalStringRepresentation), 0)));
        move(bitLength);
    }
 
    @Override
    public void writeDouble(String logicalName, int bitLength, double value, WithWriterArgs... writerArgs) throws SerializationException {
        String additionalStringRepresentation = extractAdditionalStringRepresentation(writerArgs).map(s -> " " + s).orElse("");
        boxes.offerLast(Either.left(asciiBoxWriter.boxString(logicalName, String.format("0x%0" + Math.max(bitLength / 4, 1) + "x %f%s", Double.valueOf(value).longValue(), value, additionalStringRepresentation), 0)));
        move(bitLength);
    }
 
    @Override
    public void writeBigDecimal(String logicalName, int bitLength, BigDecimal value, WithWriterArgs... writerArgs) throws SerializationException {
        String additionalStringRepresentation = extractAdditionalStringRepresentation(writerArgs).map(s -> " " + s).orElse("");
        boxes.offerLast(Either.left(asciiBoxWriter.boxString(logicalName, String.format("0x%0" + Math.max(bitLength / 4, 1) + "x %d%s", value, value, additionalStringRepresentation), 0)));
        move(bitLength);
    }
 
    @Override
    public void writeString(String logicalName, int bitLength, String encoding, String value, WithWriterArgs... writerArgs) throws SerializationException {
        String additionalStringRepresentation = extractAdditionalStringRepresentation(writerArgs).map(s -> " " + s).orElse("");
        boxes.offerLast(Either.left(asciiBoxWriter.boxString(logicalName, String.format("%s%s", value, additionalStringRepresentation), 0)));
        move(bitLength);
    }
 
    @Override
    public void writeVirtual(String logicalName, Object value, WithWriterArgs... writerArgs) throws SerializationException {
        String additionalStringRepresentation = extractAdditionalStringRepresentation(writerArgs).map(s -> " " + s).orElse("");
        AsciiBox virtualBox;
        if (value instanceof String) {
            virtualBox = asciiBoxWriterLight.boxString(logicalName, String.format("%s%s", value, additionalStringRepresentation), 0);
        } else if (value instanceof Float) {
            Float number = (Float) value;
            virtualBox = asciiBoxWriterLight.boxString(logicalName, String.format("%f%s", number, additionalStringRepresentation), 0);
        } else if (value instanceof Double) {
            Double number = (Double) value;
            virtualBox = asciiBoxWriterLight.boxString(logicalName, String.format("%f%s", number, additionalStringRepresentation), 0);
        } else if (value instanceof Number) {
            // TODO: adjust rendering
            Number number = (Number) value;
            virtualBox = asciiBoxWriterLight.boxString(logicalName, String.format("0x%x %d%s", number, number, additionalStringRepresentation), 0);
        } else if (value instanceof Boolean) {
            virtualBox = asciiBoxWriterLight.boxString(logicalName, String.format("b%d %b%s", (Boolean) value ? 1 : 0, value, additionalStringRepresentation), 0);
        } else if (value instanceof Enum) {
            Enum<?> enumValue = (Enum<?>) value;
            virtualBox = asciiBoxWriterLight.boxString(logicalName, String.format("%s%s", enumValue.name(), additionalStringRepresentation), 0);
        } else if (value instanceof Serializable) {
            Serializable serializable = (Serializable) value;
            try {
                WriteBufferBoxBased writeBuffer = new WriteBufferBoxBased(true, true);
                serializable.serialize(writeBuffer);
                virtualBox = asciiBoxWriterLight.boxBox(logicalName, writeBuffer.getBox(), 0);
            } catch (SerializationException e) {
                virtualBox = asciiBoxWriterLight.boxString(logicalName, e.getMessage(), 0);
            }
        } else {
            virtualBox = asciiBoxWriterLight.boxString(logicalName, "un-renderable", 0);
        }
        boxes.offerLast(Either.left(virtualBox));
    }
 
    @Override
    public void popContext(String logicalName, WithWriterArgs... writerArgs) {
        currentWidth += Hex.boxLineOverheat;
        Deque<AsciiBox> finalBoxes = new LinkedList<>();
        findTheBox:
        for (Either<AsciiBox, Deque<AsciiBox>> back = boxes.pollLast(); back != null; back = boxes.pollLast()) {
            if (back.isLeft()) {
                AsciiBox asciiBox = back.getLeft();
                if (omitEmptyBoxes && asciiBox.isEmpty()) {
                    continue;
                }
                finalBoxes.offerFirst(asciiBox);
            } else {
                Deque<AsciiBox> asciiBoxes = back.get();
                LinkedList<AsciiBox> reversedList = new LinkedList<>(asciiBoxes);
                Collections.reverse(reversedList);
                for (AsciiBox box : asciiBoxes) {
                    finalBoxes.offerFirst(box);
                }
                break findTheBox;
            }
        }
        if (mergeSingleBoxes && finalBoxes.size() == 1) {
            AsciiBox onlyChild = finalBoxes.remove();
            String childName = onlyChild.getBoxName();
            onlyChild = onlyChild.changeBoxName(logicalName + "/" + childName);
            if (omitEmptyBoxes && onlyChild.isEmpty()) {
                return;
            }
            boxes.offerLast(Either.left(onlyChild));
            return;
        }
        AsciiBox asciiBox = asciiBoxWriter.boxBox(logicalName, asciiBoxWriter.alignBoxes(finalBoxes, currentWidth), 0);
        if (omitEmptyBoxes && asciiBox.isEmpty()) {
            return;
        }
        boxes.offerLast(Either.left(asciiBox));
    }
 
    public AsciiBox getBox() {
        return boxes.peek().getLeft();
    }
 
    private void move(int bits) {
        pos += bits;
    }
}

V6046 Incorrect format. Incompatible types for argument 'value': '%b' is required.