package packet

import (
	"fmt"

	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/pkg/errors"
	"golang.org/x/net/icmp"
)

func FindProtocol(p []byte) (layers.IPProtocol, error) {
	version, err := FindIPVersion(p)
	if err != nil {
		return 0, err
	}
	switch version {
	case 4:
		if len(p) < ipv4HeaderLen {
			return 0, fmt.Errorf("IPv4 packet should have at least %d bytes, got %d bytes", ipv4HeaderLen, len(p))
		}
		// Protocol is in the 10th byte of IPv4 header
		return layers.IPProtocol(p[9]), nil
	case 6:
		if len(p) < ipv6HeaderLen {
			return 0, fmt.Errorf("IPv6 packet should have at least %d bytes, got %d bytes", ipv6HeaderLen, len(p))
		}
		// Next header is in the 7th byte of IPv6 header
		return layers.IPProtocol(p[6]), nil
	default:
		return 0, fmt.Errorf("unknow ip version %d", version)
	}
}

func FindIPVersion(p []byte) (uint8, error) {
	if len(p) == 0 {
		return 0, fmt.Errorf("packet length is 0")
	}
	return p[0] >> 4, nil
}

// IPDecoder decodes raw packets into IP. It can process packets sequentially without allocating
// memory for the layers, so it cannot be called concurrently.
type IPDecoder struct {
	ipv4   *layers.IPv4
	ipv6   *layers.IPv6
	layers uint8

	v4parser *gopacket.DecodingLayerParser
	v6parser *gopacket.DecodingLayerParser
}

func NewIPDecoder() *IPDecoder {
	var (
		ipv4 layers.IPv4
		ipv6 layers.IPv6
	)
	dlpv4 := gopacket.NewDecodingLayerParser(layers.LayerTypeIPv4)
	dlpv4.SetDecodingLayerContainer(gopacket.DecodingLayerSparse(nil))
	dlpv4.AddDecodingLayer(&ipv4)
	// Stop parsing when it encounter a layer that it doesn't have a parser
	dlpv4.IgnoreUnsupported = true

	dlpv6 := gopacket.NewDecodingLayerParser(layers.LayerTypeIPv6)
	dlpv6.SetDecodingLayerContainer(gopacket.DecodingLayerSparse(nil))
	dlpv6.AddDecodingLayer(&ipv6)
	dlpv6.IgnoreUnsupported = true

	return &IPDecoder{
		ipv4:     &ipv4,
		ipv6:     &ipv6,
		layers:   1,
		v4parser: dlpv4,
		v6parser: dlpv6,
	}
}

func (pd *IPDecoder) Decode(packet RawPacket) (*IP, error) {
	// Should decode to IP layer
	decoded, err := pd.decodeByVersion(packet.Data)
	if err != nil {
		return nil, err
	}

	for _, layerType := range decoded {
		switch layerType {
		case layers.LayerTypeIPv4:
			return newIPv4(pd.ipv4)
		case layers.LayerTypeIPv6:
			return newIPv6(pd.ipv6)
		}
	}
	return nil, fmt.Errorf("no ip layer is decoded")
}

func (pd *IPDecoder) decodeByVersion(packet []byte) ([]gopacket.LayerType, error) {
	version, err := FindIPVersion(packet)
	if err != nil {
		return nil, err
	}
	decoded := make([]gopacket.LayerType, 0, pd.layers)
	switch version {
	case 4:
		err = pd.v4parser.DecodeLayers(packet, &decoded)
	case 6:
		err = pd.v6parser.DecodeLayers(packet, &decoded)
	default:
		err = fmt.Errorf("unknow ip version %d", version)
	}
	if err != nil {
		return nil, err
	}
	return decoded, nil
}

// ICMPDecoder decodes raw packets into IP and ICMP. It can process packets sequentially without allocating
// memory for the layers, so it cannot be called concurrently.
type ICMPDecoder struct {
	*IPDecoder
	icmpv4 *layers.ICMPv4
	icmpv6 *layers.ICMPv6
}

func NewICMPDecoder() *ICMPDecoder {
	ipDecoder := NewIPDecoder()

	var (
		icmpv4 layers.ICMPv4
		icmpv6 layers.ICMPv6
	)
	ipDecoder.layers++
	ipDecoder.v4parser.AddDecodingLayer(&icmpv4)
	ipDecoder.v6parser.AddDecodingLayer(&icmpv6)

	return &ICMPDecoder{
		IPDecoder: ipDecoder,
		icmpv4:    &icmpv4,
		icmpv6:    &icmpv6,
	}
}

func (pd *ICMPDecoder) Decode(packet RawPacket) (*ICMP, error) {
	// Should decode to IP and optionally ICMP layer
	decoded, err := pd.decodeByVersion(packet.Data)
	if err != nil {
		return nil, err
	}

	for _, layerType := range decoded {
		switch layerType {
		case layers.LayerTypeICMPv4:
			ipv4, err := newIPv4(pd.ipv4)
			if err != nil {
				return nil, err
			}
			msg, err := icmp.ParseMessage(int(layers.IPProtocolICMPv4), append(pd.icmpv4.Contents, pd.icmpv4.Payload...))
			if err != nil {
				return nil, errors.Wrap(err, "failed to parse ICMPv4 message")
			}
			return &ICMP{
				IP:      ipv4,
				Message: msg,
			}, nil
		case layers.LayerTypeICMPv6:
			ipv6, err := newIPv6(pd.ipv6)
			if err != nil {
				return nil, err
			}
			msg, err := icmp.ParseMessage(int(layers.IPProtocolICMPv6), append(pd.icmpv6.Contents, pd.icmpv6.Payload...))
			if err != nil {
				return nil, errors.Wrap(err, "failed to parse ICMPv6")
			}
			return &ICMP{
				IP:      ipv6,
				Message: msg,
			}, nil
		}
	}
	layers := make([]string, len(decoded))
	for i, l := range decoded {
		layers[i] = l.String()
	}
	return nil, fmt.Errorf("Expect to decode IP and ICMP layers, got %s", layers)
}
