/*
 *  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.coyote.http2;

import java.util.function.IntPredicate;

import org.apache.tomcat.util.res.StringManager;

enum FrameType {
    // @formatter:off
    DATA            (  0, false,  true, null,              false),
    HEADERS         (  1, false,  true, null,               true),
    PRIORITY        (  2, false,  true, (x) -> x == 5,     false),
    RST             (  3, false,  true, (x) -> x == 4,     false),
    SETTINGS        (  4,  true, false, (x) -> x % 6 == 0,  true),
    PUSH_PROMISE    (  5, false,  true, (x) -> x >= 4,      true),
    PING            (  6,  true, false, (x) -> x == 8,     false),
    GOAWAY          (  7,  true, false, (x) -> x >= 8,     false),
    WINDOW_UPDATE   (  8,  true,  true, (x) -> x == 4,      true),
    CONTINUATION    (  9, false,  true, null,               true),
    PRIORITY_UPDATE ( 16,  true, false, (x) -> x >= 4,      true),
    UNKNOWN         (256,  true,  true, null,              false);
    // @formatter:on

    private static final StringManager sm = StringManager.getManager(FrameType.class);

    private final int id;
    private final boolean streamZero;
    private final boolean streamNonZero;
    private final IntPredicate payloadSizeValidator;
    private final boolean payloadErrorFatal;


    FrameType(int id, boolean streamZero, boolean streamNonZero, IntPredicate payloadSizeValidator,
            boolean payloadErrorFatal) {
        this.id = id;
        this.streamZero = streamZero;
        this.streamNonZero = streamNonZero;
        this.payloadSizeValidator = payloadSizeValidator;
        this.payloadErrorFatal = payloadErrorFatal;
    }


    int getId() {
        return id;
    }


    byte getIdByte() {
        return (byte) id;
    }


    void check(int streamId, int payloadSize) throws Http2Exception {
        // Is FrameType valid for the given stream?
        if (streamId == 0 && !streamZero || streamId != 0 && !streamNonZero) {
            throw new ConnectionException(sm.getString("frameType.checkStream", this), Http2Error.PROTOCOL_ERROR);
        }

        // Is the payload size valid for the given FrameType
        if (payloadSizeValidator != null && !payloadSizeValidator.test(payloadSize)) {
            if (payloadErrorFatal || streamId == 0) {
                throw new ConnectionException(
                        sm.getString("frameType.checkPayloadSize", Integer.toString(payloadSize), this),
                        Http2Error.FRAME_SIZE_ERROR);
            } else {
                throw new StreamException(
                        sm.getString("frameType.checkPayloadSize", Integer.toString(payloadSize), this),
                        Http2Error.FRAME_SIZE_ERROR, streamId);
            }
        }
    }


    static FrameType valueOf(int i) {
        return switch (i) {
            case 0 -> DATA;
            case 1 -> HEADERS;
            case 2 -> PRIORITY;
            case 3 -> RST;
            case 4 -> SETTINGS;
            case 5 -> PUSH_PROMISE;
            case 6 -> PING;
            case 7 -> GOAWAY;
            case 8 -> WINDOW_UPDATE;
            case 9 -> CONTINUATION;
            case 16 -> PRIORITY_UPDATE;
            default -> UNKNOWN;
        };
    }
}
