// Copyright 2019 Google LLC. All Rights Reserved.
//
// Licensed 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 expr

import (
	"encoding/binary"

	"github.com/metacubex/nftables/binaryutil"
	"github.com/mdlayher/netlink"
	"golang.org/x/sys/unix"
)

// Range implements range expression
type Range struct {
	Op       CmpOp
	Register uint32
	FromData []byte
	ToData   []byte
}

func (e *Range) marshal(fam byte) ([]byte, error) {
	data, err := e.marshalData(fam)
	if err != nil {
		return nil, err
	}

	return netlink.MarshalAttributes([]netlink.Attribute{
		{Type: unix.NFTA_EXPR_NAME, Data: []byte("range\x00")},
		{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
	})
}

func (e *Range) marshalData(fam byte) ([]byte, error) {
	var attrs []netlink.Attribute
	var err error
	var rangeFromData, rangeToData []byte

	if e.Register > 0 {
		attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_RANGE_SREG, Data: binaryutil.BigEndian.PutUint32(e.Register)})
	}
	attrs = append(attrs, netlink.Attribute{Type: unix.NFTA_RANGE_OP, Data: binaryutil.BigEndian.PutUint32(uint32(e.Op))})
	if len(e.FromData) > 0 {
		rangeFromData, err = nestedAttr(e.FromData, unix.NFTA_RANGE_FROM_DATA)
		if err != nil {
			return nil, err
		}
	}
	if len(e.ToData) > 0 {
		rangeToData, err = nestedAttr(e.ToData, unix.NFTA_RANGE_TO_DATA)
		if err != nil {
			return nil, err
		}
	}
	data, err := netlink.MarshalAttributes(attrs)
	if err != nil {
		return nil, err
	}
	data = append(data, rangeFromData...)
	data = append(data, rangeToData...)
	return data, nil
}

func (e *Range) unmarshal(fam byte, data []byte) error {
	ad, err := netlink.NewAttributeDecoder(data)
	if err != nil {
		return err
	}
	ad.ByteOrder = binary.BigEndian
	for ad.Next() {
		switch ad.Type() {
		case unix.NFTA_RANGE_OP:
			e.Op = CmpOp(ad.Uint32())
		case unix.NFTA_RANGE_SREG:
			e.Register = ad.Uint32()
		case unix.NFTA_RANGE_FROM_DATA:
			ad.Do(func(b []byte) error {
				ad, err := netlink.NewAttributeDecoder(b)
				if err != nil {
					return err
				}
				ad.ByteOrder = binary.BigEndian
				if ad.Next() && ad.Type() == unix.NFTA_DATA_VALUE {
					ad.Do(func(b []byte) error {
						e.FromData = b
						return nil
					})
				}
				return ad.Err()
			})
		case unix.NFTA_RANGE_TO_DATA:
			ad.Do(func(b []byte) error {
				ad, err := netlink.NewAttributeDecoder(b)
				if err != nil {
					return err
				}
				ad.ByteOrder = binary.BigEndian
				if ad.Next() && ad.Type() == unix.NFTA_DATA_VALUE {
					ad.Do(func(b []byte) error {
						e.ToData = b
						return nil
					})
				}
				return ad.Err()
			})
		}
	}
	return ad.Err()
}

func nestedAttr(data []byte, attrType uint16) ([]byte, error) {
	ae := netlink.NewAttributeEncoder()
	ae.Do(unix.NLA_F_NESTED|attrType, func() ([]byte, error) {
		nae := netlink.NewAttributeEncoder()
		nae.ByteOrder = binary.BigEndian
		nae.Bytes(unix.NFTA_DATA_VALUE, data)

		return nae.Encode()
	})
	return ae.Encode()
}
