/*
 * 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
 *
 *     http://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.activemq.artemis.tests.integration.stomp.util;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.apache.activemq.artemis.core.protocol.stomp.Stomp;

public abstract class AbstractClientStompFrame implements ClientStompFrame {

   protected static final Set<String> validCommands = new HashSet<>();
   protected String command;
   protected List<Header> headers = new ArrayList<>();
   protected Set<String> headerKeys = new HashSet<>();
   protected String body;
   protected String EOL = "\n";

   static {
      validCommands.add(Stomp.Commands.CONNECT);
      validCommands.add(Stomp.Responses.CONNECTED);
      validCommands.add(Stomp.Commands.SEND);
      validCommands.add(Stomp.Responses.RECEIPT);
      validCommands.add(Stomp.Commands.SUBSCRIBE);
      validCommands.add(Stomp.Commands.UNSUBSCRIBE);
      validCommands.add(Stomp.Responses.MESSAGE);
      validCommands.add(Stomp.Commands.BEGIN);
      validCommands.add(Stomp.Commands.COMMIT);
      validCommands.add(Stomp.Commands.ABORT);
      validCommands.add(Stomp.Commands.ACK);
      validCommands.add(Stomp.Commands.DISCONNECT);
      validCommands.add(Stomp.Responses.ERROR);
   }

   public AbstractClientStompFrame(String command) {
      this(command, true);
   }

   public AbstractClientStompFrame(String command, boolean validate) {
      if (validate && (!validate(command)))
         throw new IllegalArgumentException("Invalid command " + command);
      this.command = command;
   }

   public boolean validate(String command) {
      return validCommands.contains(command);
   }

   @Override
   public String toString() {
      StringBuilder sb = new StringBuilder("Frame: <" + command + ">" + "\n");
      Iterator<Header> iter = headers.iterator();
      while (iter.hasNext()) {
         Header h = iter.next();
         sb.append(h.key + ":" + h.val + "\n");
      }
      sb.append("\n");
      sb.append("<body>" + body + "<body>");
      return sb.toString();
   }

   @Override
   public ByteBuffer toByteBuffer() {
      return toByteBufferInternal(null);
   }

   @Override
   public ByteBuffer toByteBufferWithExtra(String str) {
      return toByteBufferInternal(str);
   }

   @Override
   public ByteBuf toNettyByteBuf() {
      return Unpooled.copiedBuffer(toByteBuffer());
   }

   @Override
   public ByteBuf toNettyByteBufWithExtras(String str) {
      return Unpooled.copiedBuffer(toByteBufferWithExtra(str));
   }

   public ByteBuffer toByteBufferInternal(String str) {
      StringBuilder sb = new StringBuilder();
      sb.append(command + EOL);
      int n = headers.size();
      for (int i = 0; i < n; i++) {
         sb.append(headers.get(i).key + ":" + headers.get(i).val + EOL);
      }
      sb.append(EOL);
      if (body != null) {
         sb.append(body);
      }
      sb.append((char) 0);
      if (str != null) {
         sb.append(str);
      }

      String data = sb.toString();

      byte[] byteValue = data.getBytes(StandardCharsets.UTF_8);

      ByteBuffer buffer = ByteBuffer.allocateDirect(byteValue.length);
      buffer.put(byteValue);

      buffer.rewind();
      return buffer;
   }

   @Override
   public boolean needsReply() {
      if (Stomp.Commands.CONNECT.equals(command) || headerKeys.contains(Stomp.Headers.RECEIPT_REQUESTED)) {
         return true;
      }
      return false;
   }

   @Override
   public ClientStompFrame setCommand(String command) {
      this.command = command;
      return this;
   }

   @Override
   public ClientStompFrame addHeader(String key, String val) {
      headers.add(new Header(key, val));
      headerKeys.add(key);
      return this;
   }

   @Override
   public ClientStompFrame setBody(String body) {
      this.body = body;
      return this;
   }

   @Override
   public String getBody() {
      return body;
   }

   private class Header {

      public String key;
      public String val;

      private Header(String key, String val) {
         this.key = key;
         this.val = val;
      }
   }

   @Override
   public String getCommand() {
      return command;
   }

   @Override
   public String getHeader(String header) {
      if (headerKeys.contains(header)) {
         Iterator<Header> iter = headers.iterator();
         while (iter.hasNext()) {
            Header h = iter.next();
            if (h.key.equals(header)) {
               return h.val;
            }
         }
      }
      return null;
   }

}
