package multiaddr

import (
	
	
	
	
	
	
	
	
	
	

	
	
	mh 
)

type Transcoder interface {
	// Validates and encodes to bytes a multiaddr that's in the string representation.
	StringToBytes(string) ([]byte, error)
	// Validates and decodes to a string a multiaddr that's in the bytes representation.
	BytesToString([]byte) (string, error)
	// Validates bytes when parsing a multiaddr that's already in the bytes representation.
	ValidateBytes([]byte) error
}

func (
	 func(string) ([]byte, error),
	 func([]byte) (string, error),
	 func([]byte) error,
) Transcoder {
	return twrp{, , }
}

type twrp struct {
	strtobyte func(string) ([]byte, error)
	bytetostr func([]byte) (string, error)
	validbyte func([]byte) error
}

func ( twrp) ( string) ([]byte, error) {
	return .strtobyte()
}
func ( twrp) ( []byte) (string, error) {
	return .bytetostr()
}

func ( twrp) ( []byte) error {
	if .validbyte == nil {
		return nil
	}
	return .validbyte()
}

var TranscoderIP4 = NewTranscoderFromFunctions(ip4StB, ip4BtS, nil)
var TranscoderIP6 = NewTranscoderFromFunctions(ip6StB, ip6BtS, nil)
var TranscoderIP6Zone = NewTranscoderFromFunctions(ip6zoneStB, ip6zoneBtS, ip6zoneVal)
var TranscoderIPCIDR = NewTranscoderFromFunctions(ipcidrStB, ipcidrBtS, ipcidrValidate)

func ipcidrBtS( []byte) (string, error) {
	if  := ipcidrValidate();  != nil {
		return "", 
	}
	return strconv.Itoa(int([0])), nil
}

func ipcidrStB( string) ([]byte, error) {
	,  := strconv.ParseUint(, 10, 8)
	if  != nil {
		return nil, 
	}
	return []byte{byte(uint8())}, nil
}

func ipcidrValidate( []byte) error {
	if len() != 1 {
		return fmt.Errorf("invalid length (should be == 1)")
	}
	return nil
}

func ip4StB( string) ([]byte, error) {
	 := net.ParseIP().To4()
	if  == nil {
		return nil, fmt.Errorf("failed to parse ip4 addr: %s", )
	}
	return , nil
}

func ip6zoneStB( string) ([]byte, error) {
	if len() == 0 {
		return nil, fmt.Errorf("empty ip6zone")
	}
	if strings.Contains(, "/") {
		return nil, fmt.Errorf("IPv6 zone ID contains '/': %s", )
	}
	return []byte(), nil
}

func ip6zoneBtS( []byte) (string, error) {
	if len() == 0 {
		return "", fmt.Errorf("invalid length (should be > 0)")
	}
	return string(), nil
}

func ip6zoneVal( []byte) error {
	if len() == 0 {
		return fmt.Errorf("invalid length (should be > 0)")
	}
	// Not supported as this would break multiaddrs.
	if bytes.IndexByte(, '/') >= 0 {
		return fmt.Errorf("IPv6 zone ID contains '/': %s", string())
	}
	return nil
}

func ip6StB( string) ([]byte, error) {
	 := net.ParseIP().To16()
	if  == nil {
		return nil, fmt.Errorf("failed to parse ip6 addr: %s", )
	}
	return , nil
}

func ip6BtS( []byte) (string, error) {
	 := net.IP()
	if  := .To4();  != nil {
		// Go fails to prepend the `::ffff:` part.
		return "::ffff:" + .String(), nil
	}
	return .String(), nil
}

func ip4BtS( []byte) (string, error) {
	return net.IP().String(), nil
}

var TranscoderPort = NewTranscoderFromFunctions(portStB, portBtS, nil)

func portStB( string) ([]byte, error) {
	,  := strconv.ParseUint(, 10, 16)
	if  != nil {
		return nil, fmt.Errorf("failed to parse port addr: %s", )
	}
	 := make([]byte, 2)
	binary.BigEndian.PutUint16(, uint16())
	return , nil
}

func portBtS( []byte) (string, error) {
	 := binary.BigEndian.Uint16()
	return strconv.FormatUint(uint64(), 10), nil
}

var TranscoderOnion = NewTranscoderFromFunctions(onionStB, onionBtS, onionValidate)

func onionStB( string) ([]byte, error) {
	 := strings.Split(, ":")
	if len() != 2 {
		return nil, fmt.Errorf("failed to parse onion addr: %s does not contain a port number", )
	}

	,  := base32.StdEncoding.DecodeString(strings.ToUpper([0]))
	if  != nil {
		return nil, fmt.Errorf("failed to decode base32 onion addr: %s %s", , )
	}

	// onion address without the ".onion" substring are 10 bytes long
	if len() != 10 {
		return nil, fmt.Errorf("failed to parse onion addr: %s not a Tor onion address", )
	}

	// onion port number
	,  := strconv.ParseUint([1], 10, 16)
	if  != nil {
		return nil, fmt.Errorf("failed to parse onion addr: %s", )
	}
	if  == 0 {
		return nil, fmt.Errorf("failed to parse onion addr: %s", "non-zero port")
	}

	 := make([]byte, 2)
	binary.BigEndian.PutUint16(, uint16())
	 := []byte{}
	 = append(, ...)
	 = append(, ...)
	return , nil
}

func onionBtS( []byte) (string, error) {
	 := strings.ToLower(base32.StdEncoding.EncodeToString([0:10]))
	 := binary.BigEndian.Uint16([10:12])
	if  == 0 {
		return "", fmt.Errorf("failed to parse onion addr: %s", "non-zero port")
	}
	return  + ":" + strconv.FormatUint(uint64(), 10), nil
}

func onionValidate( []byte) error {
	if len() != 12 {
		return fmt.Errorf("invalid len for onion addr: got %d expected 12", len())
	}
	 := binary.BigEndian.Uint16([10:12])
	if  == 0 {
		return fmt.Errorf("invalid port 0 for onion addr")
	}
	return nil
}

var TranscoderOnion3 = NewTranscoderFromFunctions(onion3StB, onion3BtS, onion3Validate)

func onion3StB( string) ([]byte, error) {
	 := strings.Split(, ":")
	if len() != 2 {
		return nil, fmt.Errorf("failed to parse onion addr: %s does not contain a port number", )
	}

	// onion address without the ".onion" substring
	if len([0]) != 56 {
		return nil, fmt.Errorf("failed to parse onion addr: %s not a Tor onionv3 address. len == %d", , len([0]))
	}
	,  := base32.StdEncoding.DecodeString(strings.ToUpper([0]))
	if  != nil {
		return nil, fmt.Errorf("failed to decode base32 onion addr: %s %s", , )
	}

	// onion port number
	,  := strconv.ParseUint([1], 10, 16)
	if  != nil {
		return nil, fmt.Errorf("failed to parse onion addr: %s", )
	}
	if  == 0 {
		return nil, fmt.Errorf("failed to parse onion addr: %s", "non-zero port")
	}

	 := make([]byte, 2)
	binary.BigEndian.PutUint16(, uint16())
	 := []byte{}
	 = append(, [0:35]...)
	 = append(, ...)
	return , nil
}

func onion3BtS( []byte) (string, error) {
	 := strings.ToLower(base32.StdEncoding.EncodeToString([0:35]))
	 := binary.BigEndian.Uint16([35:37])
	if  < 1 {
		return "", fmt.Errorf("failed to parse onion addr: %s", "port less than 1")
	}
	 :=  + ":" + strconv.FormatUint(uint64(), 10)
	return , nil
}

func onion3Validate( []byte) error {
	if len() != 37 {
		return fmt.Errorf("invalid len for onion addr: got %d expected 37", len())
	}
	 := binary.BigEndian.Uint16([35:37])
	if  == 0 {
		return fmt.Errorf("invalid port 0 for onion addr")
	}
	return nil
}

var TranscoderGarlic64 = NewTranscoderFromFunctions(garlic64StB, garlic64BtS, garlic64Validate)

// i2p uses an alternate character set for base64 addresses. This returns an appropriate encoder.
var garlicBase64Encoding = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~")

func garlic64StB( string) ([]byte, error) {
	,  := garlicBase64Encoding.DecodeString()
	if  != nil {
		return nil, fmt.Errorf("failed to decode base64 i2p addr: %s %s", , )
	}

	if  := garlic64Validate();  != nil {
		return nil, 
	}
	return , nil
}

func garlic64BtS( []byte) (string, error) {
	if  := garlic64Validate();  != nil {
		return "", 
	}
	 := garlicBase64Encoding.EncodeToString()
	return , nil
}

func garlic64Validate( []byte) error {
	// A garlic64 address will always be greater than 386 bytes long when encoded.
	if len() < 386 {
		return fmt.Errorf("failed to validate garlic addr: %s not an i2p base64 address. len: %d", , len())
	}
	return nil
}

var TranscoderGarlic32 = NewTranscoderFromFunctions(garlic32StB, garlic32BtS, garlic32Validate)

var garlicBase32Encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")

func garlic32StB( string) ([]byte, error) {
	for len()%8 != 0 {
		 += "="
	}
	,  := garlicBase32Encoding.DecodeString()
	if  != nil {
		return nil, fmt.Errorf("failed to decode base32 garlic addr: %s, err: %v len: %v", , , len())
	}

	if  := garlic32Validate();  != nil {
		return nil, 
	}
	return , nil
}

func garlic32BtS( []byte) (string, error) {
	if  := garlic32Validate();  != nil {
		return "", 
	}
	return strings.TrimRight(garlicBase32Encoding.EncodeToString(), "="), nil
}

func garlic32Validate( []byte) error {
	// an i2p address with encrypted leaseset has len >= 35 bytes
	// all other addresses will always be exactly 32 bytes
	// https://geti2p.net/spec/b32encrypted
	if len() < 35 && len() != 32 {
		return fmt.Errorf("failed to validate garlic addr: %s not an i2p base32 address. len: %d", , len())
	}
	return nil
}

var TranscoderP2P = NewTranscoderFromFunctions(p2pStB, p2pBtS, p2pVal)

// The encoded peer ID can either be a CID of a key or a raw multihash (identity
// or sha256-256).
func p2pStB( string) ([]byte, error) {
	// check if the address is a base58 encoded sha256 or identity multihash
	if strings.HasPrefix(, "Qm") || strings.HasPrefix(, "1") {
		,  := mh.FromB58String()
		if  != nil {
			return nil, fmt.Errorf("failed to parse p2p addr: %s %s", , )
		}
		if  := p2pVal();  != nil {
			return nil, 
		}
		return , nil
	}

	// check if the address is a CID
	,  := cid.Decode()
	if  != nil {
		return nil, fmt.Errorf("failed to parse p2p addr: %s %s", , )
	}

	if  := .Type();  == cid.Libp2pKey {
		if  := p2pVal(.Hash());  != nil {
			return nil, 
		}
		return .Hash(), nil
	} else {
		return nil, fmt.Errorf("failed to parse p2p addr: %s has the invalid codec %d", , )
	}
}

func p2pVal( []byte) error {
	,  := mh.Decode([]byte())
	if  != nil {
		return fmt.Errorf("invalid multihash: %s", )
	}
	// Peer IDs require either sha256 or identity multihash
	// https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md#peer-ids
	if .Code != mh.SHA2_256 && .Code != mh.IDENTITY {
		return fmt.Errorf("invalid multihash code %d expected sha-256 or identity", .Code)
	}
	// This check should ideally be in multihash. sha256 digest lengths MUST be 32
	if .Code == mh.SHA2_256 && .Length != 32 {
		return fmt.Errorf("invalid digest length %d for sha256 addr: expected 32", .Length)
	}
	return nil
}

func p2pBtS( []byte) (string, error) {
	,  := mh.Cast()
	if  != nil {
		return "", 
	}
	return .B58String(), nil
}

var TranscoderUnix = NewTranscoderFromFunctions(unixStB, unixBtS, unixValidate)

func unixStB( string) ([]byte, error) {
	return []byte(), nil
}

func unixBtS( []byte) (string, error) {
	return string(), nil
}

func unixValidate( []byte) error {
	// The string to bytes parser requires that all Path protocols begin with a '/'
	// file://./codec.go#L49
	if len() < 2 {
		return fmt.Errorf("byte slice too short: %d", len())
	}
	if [0] != '/' {
		return errors.New("path protocol must begin with '/'")
	}
	if [len()-1] == '/' {
		return errors.New("unix socket path must not end in '/'")
	}
	return nil
}

var TranscoderDns = NewTranscoderFromFunctions(dnsStB, dnsBtS, dnsVal)

func dnsVal( []byte) error {
	if len() == 0 {
		return fmt.Errorf("empty dns addr")
	}
	if bytes.IndexByte(, '/') >= 0 {
		return fmt.Errorf("domain name %q contains a slash", string())
	}
	return nil
}

func dnsStB( string) ([]byte, error) {
	 := []byte()
	if  := dnsVal();  != nil {
		return nil, 
	}
	return , nil
}

func dnsBtS( []byte) (string, error) {
	return string(), nil
}

var TranscoderCertHash = NewTranscoderFromFunctions(certHashStB, certHashBtS, validateCertHash)

func certHashStB( string) ([]byte, error) {
	, ,  := multibase.Decode()
	if  != nil {
		return nil, 
	}
	if ,  := mh.Decode();  != nil {
		return nil, 
	}
	return , nil
}

func certHashBtS( []byte) (string, error) {
	return multibase.Encode(multibase.Base64url, )
}

func validateCertHash( []byte) error {
	,  := mh.Decode()
	return 
}

var TranscoderHTTPPath = NewTranscoderFromFunctions(httpPathStB, httpPathBtS, validateHTTPPath)

func httpPathStB( string) ([]byte, error) {
	,  := url.QueryUnescape()
	if  != nil {
		return nil, 
	}
	if len() == 0 {
		return nil, fmt.Errorf("empty http path is not allowed")
	}
	return []byte(), 
}

func httpPathBtS( []byte) (string, error) {
	if len() == 0 {
		return "", fmt.Errorf("empty http path is not allowed")
	}
	return url.QueryEscape(string()), nil
}

func validateHTTPPath( []byte) error {
	if len() == 0 {
		return fmt.Errorf("empty http path is not allowed")
	}
	return nil // We can represent any byte slice when we escape it.
}

var TranscoderMemory = NewTranscoderFromFunctions(memoryStB, memoryBtS, memoryValidate)

func memoryStB( string) ([]byte, error) {
	,  := strconv.ParseUint(, 10, 64)
	if  != nil {
		return nil, 
	}
	 := make([]byte, 8)
	binary.BigEndian.PutUint64(, )
	return , nil
}

func memoryBtS( []byte) (string, error) {
	if len() != 8 {
		return "", fmt.Errorf("expected uint64, only found %d bits", len()*8)
	}
	 := binary.BigEndian.Uint64()
	return strconv.FormatUint(, 10), nil
}

func memoryValidate( []byte) error {
	// Ensure the byte array is exactly 8 bytes long for a uint64 in big-endian format
	if len() != 8 {
		return errors.New("invalid length: must be exactly 8 bytes")
	}

	return nil
}