package storage

import (
	"errors"
	"net"
	"path"

	"github.com/bradfitz/gomemcache/memcache"
	"github.com/buraksezer/consistent"
	"github.com/cespare/xxhash"
)

type MemcacheCluster struct {
	db *memcache.Client
}

type serverSelector struct {
	hash *consistent.Consistent
}

func (ss *serverSelector) PickServer(key string) (net.Addr, error) {
	return &netAddr{
		network: "tcp",
		addr:    ss.hash.LocateKey([]byte(key)).String(),
	}, nil
}

func (ss *serverSelector) Each(each func(net.Addr) error) error {
	for _, member := range ss.hash.GetMembers() {
		if err := each(&netAddr{
			network: "tcp",
			addr:    member.String(),
		}); err != nil {
			return err
		}
	}
	return nil
}

type hasher struct{}

func (h hasher) Sum64(data []byte) uint64 {
	return xxhash.Sum64(data)
}

func NewMemcacheCluster(addr string, addrs ...string) (*MemcacheCluster, error) {
	cfg := consistent.Config{
		PartitionCount:    71,
		ReplicationFactor: 20,
		Load:              1.25,
		Hasher:            hasher{},
	}
	hash := consistent.New(nil, cfg)
	for _, addr := range append(addrs, addr) {
		hash.Add(&netAddr{addr: addr})
	}
	ss := &serverSelector{
		hash: hash,
	}
	if err := ss.Each(func(addr net.Addr) error {
		conn, err := net.Dial("tcp", addr.String())
		if err != nil {
			return err
		}
		return conn.Close()
	}); err != nil {
		return nil, err
	}
	db := memcache.NewFromSelector(ss)
	return &MemcacheCluster{db: db}, nil
}

func (mc *MemcacheCluster) List(ns []string, limits ...string) ([]string, error) {
	return nil, errors.New("not impl")
}

func (mc *MemcacheCluster) Get(key string, ns ...string) ([]byte, error) {
	namespace := resolveNamespace(ns)
	v, err := mc.db.Get(path.Join(namespace, key))
	return v.Value, err
}

func (mc *MemcacheCluster) Set(key string, value []byte, ns ...string) error {
	namespace := resolveNamespace(ns)
	return mc.db.Set(&memcache.Item{
		Key:   path.Join(namespace, key),
		Value: value,
	})
}

func (mc *MemcacheCluster) Close() error {
	return mc.db.FlushAll()
}
