package mafmt

import (
	

	ma 
)

// Define a dns4 format multiaddr
var DNS4 = Base(ma.P_DNS4)

// Define a dns6 format multiaddr
var DNS6 = Base(ma.P_DNS6)

// Define a dnsaddr, dns, dns4 or dns6 format multiaddr
var DNS = Or(
	Base(ma.P_DNS),
	Base(ma.P_DNSADDR),
	DNS4,
	DNS6,
)

// Define IP as either ipv4 or ipv6
var IP = Or(Base(ma.P_IP4), Base(ma.P_IP6))

// Define TCP as 'tcp' on top of either ipv4 or ipv6, or dns equivalents.
var TCP = Or(
	And(DNS, Base(ma.P_TCP)),
	And(IP, Base(ma.P_TCP)),
)

// Define UDP as 'udp' on top of either ipv4 or ipv6, or dns equivalents.
var UDP = Or(
	And(DNS, Base(ma.P_UDP)),
	And(IP, Base(ma.P_UDP)),
)

// Define UTP as 'utp' on top of udp (on top of ipv4 or ipv6).
var UTP = And(UDP, Base(ma.P_UTP))

// Define QUIC as 'quic' on top of udp (on top of ipv4 or ipv6)
var QUIC = And(UDP, Base(ma.P_QUIC))

// Define unreliable transport as udp
var Unreliable = Or(UDP)

// Now define a Reliable transport as either tcp or utp or quic
var Reliable = Or(TCP, UTP, QUIC)

// P2P can run over any reliable underlying transport protocol
var P2P = And(Reliable, Base(ma.P_P2P))

// IPFS can run over any reliable underlying transport protocol
//
// Deprecated: use P2P
var IPFS = P2P

// Define http over TCP or DNS or http over DNS format multiaddr
var HTTP = Or(
	And(TCP, Base(ma.P_HTTP)),
	And(IP, Base(ma.P_HTTP)),
	And(DNS, Base(ma.P_HTTP)),
)

// Define https over TCP or DNS or https over DNS format multiaddr
var HTTPS = Or(
	And(TCP, Base(ma.P_HTTPS)),
	And(IP, Base(ma.P_HTTPS)),
	And(DNS, Base(ma.P_HTTPS)),
)

// Define p2p-webrtc-direct over HTTP or p2p-webrtc-direct over HTTPS format multiaddr
var WebRTCDirect = Or(
	And(HTTP, Base(ma.P_P2P_WEBRTC_DIRECT)),
	And(HTTPS, Base(ma.P_P2P_WEBRTC_DIRECT)))

const (
	or  = iota
	and = iota
)

func ( ...Pattern) Pattern {
	return &pattern{
		Op:   and,
		Args: ,
	}
}

func ( ...Pattern) Pattern {
	return &pattern{
		Op:   or,
		Args: ,
	}
}

type Pattern interface {
	Matches(ma.Multiaddr) bool
	partialMatch([]ma.Protocol) (bool, []ma.Protocol)
	String() string
}

type pattern struct {
	Args []Pattern
	Op   int
}

func ( *pattern) ( ma.Multiaddr) bool {
	,  := .partialMatch(.Protocols())
	return  && len() == 0
}

func ( *pattern) ( []ma.Protocol) (bool, []ma.Protocol) {
	switch .Op {
	case or:
		for ,  := range .Args {
			,  := .partialMatch()
			if  {
				return true, 
			}
		}
		return false, nil
	case and:
		if len() < len(.Args) {
			return false, nil
		}

		for  := 0;  < len(.Args); ++ {
			,  := .Args[].partialMatch()
			if ! {
				return false, nil
			}

			 = 
		}

		return true, 
	default:
		panic("unrecognized pattern operand")
	}
}

func ( *pattern) () string {
	var  []string
	for ,  := range .Args {
		 = append(, .String())
	}

	switch .Op {
	case and:
		return strings.Join(, "/")
	case or:
		return "{" + strings.Join(, "|") + "}"
	default:
		panic("unrecognized pattern op!")
	}
}

type Base int

func ( Base) ( ma.Multiaddr) bool {
	 := .Protocols()
	return [0].Code == int() && len() == 1
}

func ( Base) ( []ma.Protocol) (bool, []ma.Protocol) {
	if len() == 0 {
		return false, nil
	}
	if [0].Code == int() {
		return true, [1:]
	}
	return false, nil
}

func ( Base) () string {
	return ma.ProtocolWithCode(int()).Name
}