/*
 * Copyright 2014 The Netty Project
 *
 * The Netty Project 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 io.netty.handler.codec.http2;

import io.netty.util.internal.UnstableApi;

/**
 * Provides utility methods for accessing specific flags as defined by the HTTP/2 spec.
 */
@UnstableApi
public final class Http2Flags {
    public static final short END_STREAM = 0x1;
    public static final short END_HEADERS = 0x4;
    public static final short ACK = 0x1;
    public static final short PADDED = 0x8;
    public static final short PRIORITY = 0x20;

    private short value;

    public Http2Flags() {
    }

    public Http2Flags(short value) {
        this.value = value;
    }

    /**
     * Gets the underlying flags value.
     */
    public short value() {
        return value;
    }

    /**
     * Determines whether the {@link #END_STREAM} flag is set. Only applies to DATA and HEADERS
     * frames.
     */
    public boolean endOfStream() {
        return isFlagSet(END_STREAM);
    }

    /**
     * Determines whether the {@link #END_HEADERS} flag is set. Only applies for HEADERS,
     * PUSH_PROMISE, and CONTINUATION frames.
     */
    public boolean endOfHeaders() {
        return isFlagSet(END_HEADERS);
    }

    /**
     * Determines whether the flag is set indicating the presence of the exclusive, stream
     * dependency, and weight fields in a HEADERS frame.
     */
    public boolean priorityPresent() {
        return isFlagSet(PRIORITY);
    }

    /**
     * Determines whether the flag is set indicating that this frame is an ACK. Only applies for
     * SETTINGS and PING frames.
     */
    public boolean ack() {
        return isFlagSet(ACK);
    }

    /**
     * For frames that include padding, indicates if the {@link #PADDED} field is present. Only
     * applies to DATA, HEADERS, PUSH_PROMISE and CONTINUATION frames.
     */
    public boolean paddingPresent() {
        return isFlagSet(PADDED);
    }

    /**
     * Gets the number of bytes expected for the priority fields of the payload. This is determined
     * by the {@link #priorityPresent()} flag.
     */
    public int getNumPriorityBytes() {
        return priorityPresent() ? 5 : 0;
    }

    /**
     * Gets the length in bytes of the padding presence field expected in the payload. This is
     * determined by the {@link #paddingPresent()} flag.
     */
    public int getPaddingPresenceFieldLength() {
        return paddingPresent() ? 1 : 0;
    }

    /**
     * Sets the {@link #END_STREAM} flag.
     */
    public Http2Flags endOfStream(boolean endOfStream) {
        return setFlag(endOfStream, END_STREAM);
    }

    /**
     * Sets the {@link #END_HEADERS} flag.
     */
    public Http2Flags endOfHeaders(boolean endOfHeaders) {
        return setFlag(endOfHeaders, END_HEADERS);
    }

    /**
     * Sets the {@link #PRIORITY} flag.
     */
    public Http2Flags priorityPresent(boolean priorityPresent) {
        return setFlag(priorityPresent, PRIORITY);
    }

    /**
     * Sets the {@link #PADDED} flag.
     */
    public Http2Flags paddingPresent(boolean paddingPresent) {
        return setFlag(paddingPresent, PADDED);
    }

    /**
     * Sets the {@link #ACK} flag.
     */
    public Http2Flags ack(boolean ack) {
        return setFlag(ack, ACK);
    }

    /**
     * Generic method to set any flag.
     * @param on if the flag should be enabled or disabled.
     * @param mask the mask that identifies the bit for the flag.
     * @return this instance.
     */
    public Http2Flags setFlag(boolean on, short mask) {
        if (on) {
            value |= mask;
        } else {
            value &= ~mask;
        }
        return this;
    }

    /**
     * Indicates whether or not a particular flag is set.
     * @param mask the mask identifying the bit for the particular flag being tested
     * @return {@code true} if the flag is set
     */
    public boolean isFlagSet(short mask) {
        return (value & mask) != 0;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + value;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }

        return value == ((Http2Flags) obj).value;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("value = ").append(value).append(" (");
        if (ack()) {
            builder.append("ACK,");
        }
        if (endOfHeaders()) {
            builder.append("END_OF_HEADERS,");
        }
        if (endOfStream()) {
            builder.append("END_OF_STREAM,");
        }
        if (priorityPresent()) {
            builder.append("PRIORITY_PRESENT,");
        }
        if (paddingPresent()) {
            builder.append("PADDING_PRESENT,");
        }
        builder.append(')');
        return builder.toString();
    }
}
