/**
 *  Copyright 2005-2016 Red Hat, Inc.
 *
 *  Red Hat 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 io.fabric8.gateway.handlers.detecting.protocol.stomp;

import io.fabric8.gateway.handlers.detecting.protocol.Ascii;
import org.vertx.java.core.buffer.Buffer;

import java.io.UnsupportedEncodingException;
import java.util.*;

import static io.fabric8.gateway.handlers.detecting.protocol.Ascii.ascii;
import static io.fabric8.gateway.handlers.detecting.protocol.BufferSupport.startsWith;
import static io.fabric8.gateway.handlers.detecting.protocol.stomp.Constants.*;

/**
 * A STOMP protocol frame.
 *
 */
public class StompFrame {

    public static final Buffer NO_DATA = new Buffer(new byte[]{});

    static public class HeaderEntry {
        public final Ascii key;
        public final Ascii value;

        public HeaderEntry(Ascii key, Ascii value) {
            this.key = key;
            this.value = value;
        }

        public Ascii getKey() {
            return key;
        }

        public Ascii getValue() {
            return value;
        }

        @Override
        public String toString() {
            return "" + key +
                    "=" + value;
        }
    }

    private Ascii action;
    private ArrayList<HeaderEntry> headerList;
    private HashMap<Ascii, Ascii> headerMap = new HashMap<Ascii, Ascii>(16);
    private Buffer content = NO_DATA;

    public StompFrame() {
    }

    public StompFrame(Ascii action) {
        this.action = action;
    }

    public StompFrame clone() {
        StompFrame rc = new StompFrame(action);
        if( headerList!=null ) {
            rc.headerList = new ArrayList<HeaderEntry>(headerList);
            rc.headerMap = null;
        } else {
            rc.headerMap = new HashMap<Ascii,Ascii>(headerMap);
            rc.headerList = null;
        }
        rc.content = content;
        return rc;
    }

    public Ascii action() {
        return action;
    }

    public StompFrame action(Ascii action) {
        assert action != null;
        this.action = action;
        return this;
    }

    public Buffer content() {
        return this.content;
    }

    public StompFrame content(Buffer content) {
        assert content != null;
        this.content = content;
        return this;
    }

    public String contentAsString() {
        return content.getString(0, content.length(), "UTF-8");
    }

    public Map<Ascii, Ascii> headerMap() {
        return headerMap(Collections.EMPTY_SET);
    }

    public Map<Ascii, Ascii> headerMap(Set<Ascii> reversedHeaderHandling) {
        if( headerMap==null ) {
            headerMap = new HashMap<Ascii, Ascii>();
            for (HeaderEntry HeaderEntry : headerList) {
                final Ascii key = HeaderEntry.getKey();
                Ascii old = headerMap.put(key, HeaderEntry.getValue());
                if( old !=null && !reversedHeaderHandling.contains(key) ) {
                    headerMap.put(key, old);
                }
            }
            headerList = null;
        }
        return headerMap;
    }

    public List<HeaderEntry> headerList() {
        if( headerList==null ) {
            for (Map.Entry<Ascii,Ascii> entry : headerMap.entrySet()) {
                headerList.add(new HeaderEntry(entry.getKey(), entry.getValue()));
            }
            headerMap = null;
        }
        return headerList;
    }

    public void addHeader(Ascii key, Ascii value) {
        if( headerList!=null ) {
            headerList.add(0, new HeaderEntry(key, value));
        } else {
            headerMap.put(key, value);
        }
    }

    String getHeaderAsString(Ascii key) {
        Ascii header = getHeader(key);
        if( header !=null ) {
            return decodeHeader(header.toBuffer());
        }
        return null;
    }

    public Ascii getHeader(Ascii key) {
        if( headerList!=null ) {
            for (HeaderEntry HeaderEntry : headerList) {
                if( HeaderEntry.getKey().equals(key) ) {
                    return HeaderEntry.getValue();
                }
            }
            return null;
        } else {
            return headerMap.get(key);
        }
    }

    public void clearHeaders() {
        if( headerList!=null) {
            headerList.clear();
        } else {
            headerMap.clear();
        }
    }

    public void setHeaders(ArrayList<HeaderEntry> values) {
        headerList = values;
        headerMap = null;
    }
    /*
    public Buffer toBuffer() {
        return toBuffer(true);
    }
    */

    /*
    public Buffer toBuffer(boolean includeBody) {
        try {
            DataByteArrayOutputStream out = new DataByteArrayOutputStream();
            write(out, includeBody);
            return out.toBuffer();
        } catch (IOException e) {
            throw new RuntimeException(e); // not expected to occur.
        }
    }
    */

    /*
    private void write(DataOutput out, Buffer buffer) throws IOException {
        out.write(buffer.data, buffer.offset, buffer.length);
    }

    public void write(DataOutput out) throws IOException {
        write(out, true);
    }
    */

    public void addContentLengthHeader() {
        addHeader(CONTENT_LENGTH, new Ascii(Integer.toString(content.length())));
    }
    /*
    public int size() {
        int rc = action.length() + 1;
        if( headerList!=null ) {
            for (HeaderEntry entry : headerList) {
                rc += entry.getKey().length() + entry.getValue().length() + 2;
            }
        } else {
            for (Map.Entry<Ascii,Ascii> entry : headerMap.entrySet()) {
                rc += entry.getKey().length() + entry.getValue().length() + 2;
            }
        }
        rc += content.length() + 3;
        return rc;
    }
    */

    /*
    public void write(DataOutput out, boolean includeBody) throws IOException {
        write(out, action);
        out.writeByte(NEWLINE_BYTE);

        if( headerList!=null ) {
            for (HeaderEntry entry : headerList) {
                write(out, entry.getKey());
                out.writeByte(COLON_BYTE);
                write(out, entry.getValue());
                out.writeByte(NEWLINE_BYTE);
            }
        } else {
            for (Map.Entry<Ascii,Ascii> entry : headerMap.entrySet()) {
                write(out, entry.getKey());
                out.writeByte(COLON_BYTE);
                write(out, entry.getValue());
                out.writeByte(NEWLINE_BYTE);
            }
        }

        //denotes end of headers with a new line
        out.writeByte(NEWLINE_BYTE);
        if (includeBody) {
            write(out, content);
            out.writeByte(NULL_BYTE);
            out.writeByte(NEWLINE_BYTE);
        }
    }
    */
    /*
    public String toString() {
        return toBuffer(false).ascii().toString();
    }

    public String errorMessage() {
        Ascii value = getHeader(MESSAGE_HEADER);
        if (value != null) {
            return decodeHeader(value);
        } else {
            return contentAsString();
        }
    }
    */

    public static String decodeHeader(Buffer value) {
        if (value == null)
            return null;

        Buffer rc = new Buffer(value.length());
        int pos = 0;
        int max = value.length();
        while (pos < max) {
            if (startsWith(value, pos, ESCAPE_ESCAPE_SEQ.toBuffer())) {
                rc.appendByte(ESCAPE_BYTE);
                pos += 2;
            } else if (startsWith(value, pos, COLON_ESCAPE_SEQ.toBuffer())) {
                rc.appendByte(COLON_BYTE);
                pos += 2;
            } else if (startsWith(value, pos, NEWLINE_ESCAPE_SEQ.toBuffer())) {
                rc.appendByte(NEWLINE_BYTE);
                pos += 2;
            } else {
                rc.appendByte(value.getByte(pos));
                pos += 1;
            }
        }
        return rc.toString();
    }


    public static Ascii encodeHeader(String value) {
        if (value == null)
            return null;
        try {
            byte[] data = value.getBytes("UTF-8");
            Buffer rc = new Buffer(data.length);
            for (byte d : data) {
                switch (d) {
                    case ESCAPE_BYTE:
                        rc.appendBuffer(ESCAPE_ESCAPE_SEQ.toBuffer());
                        break;
                    case COLON_BYTE:
                        rc.appendBuffer(COLON_ESCAPE_SEQ.toBuffer());
                        break;
                    case NEWLINE_BYTE:
                        rc.appendBuffer(COLON_ESCAPE_SEQ.toBuffer());
                        break;
                    default:
                        rc.appendByte(d);
                }
            }
            return ascii(rc);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e); // not expected.
        }
    }

    public static Map<Ascii, Ascii> encodeHeaders(Map<String, String> headers) {
        if(headers==null)
            return null;
        HashMap<Ascii, Ascii> rc = new HashMap<Ascii, Ascii>(headers.size());
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            rc.put(StompFrame.encodeHeader(entry.getKey()), StompFrame.encodeHeader(entry.getValue()));
        }
        return rc;
    }
}
