package ble

// NewService creates and initialize a new Service using u as it's UUID.
func NewService(u UUID) *Service {
	return &Service{UUID: u}
}

// NewDescriptor creates and returns a Descriptor.
func NewDescriptor(u UUID) *Descriptor {
	return &Descriptor{UUID: u}
}

// NewCharacteristic creates and returns a Characteristic.
func NewCharacteristic(u UUID) *Characteristic {
	return &Characteristic{UUID: u}
}

// Property ...
type Property int

// Characteristic property flags (spec 3.3.3.1)
const (
	CharBroadcast   Property = 0x01 // may be brocasted
	CharRead        Property = 0x02 // may be read
	CharWriteNR     Property = 0x04 // may be written to, with no reply
	CharWrite       Property = 0x08 // may be written to, with a reply
	CharNotify      Property = 0x10 // supports notifications
	CharIndicate    Property = 0x20 // supports Indications
	CharSignedWrite Property = 0x40 // supports signed write
	CharExtended    Property = 0x80 // supports extended properties
)

// A Profile is composed of one or more services necessary to fulfill a use case.
type Profile struct {
	Services []*Service
}

// Find searches discovered profile for the specified target's type and UUID.
// The target must has the type of *Service, *Characteristic, or *Descriptor.
func (p *Profile) Find(target interface{}) interface{} {
	switch t := target.(type) {
	case *Service:
		return p.FindService(t)
	case *Characteristic:
		return p.FindCharacteristic(t)
	case *Descriptor:
		return p.FindDescriptor(t)
	default:
		return nil
	}
}

// FindService searches discoverd profile for the specified service and UUID
func (p *Profile) FindService(service *Service) *Service {
	for _, s := range p.Services {
		if s.UUID.Equal(service.UUID) {
			return s
		}
	}
	return nil
}

// FindCharacteristic searches discoverd profile for the specified characteristic and UUID
func (p *Profile) FindCharacteristic(char *Characteristic) *Characteristic {
	for _, s := range p.Services {
		for _, c := range s.Characteristics {
			if c.UUID.Equal(char.UUID) {
				return c
			}
		}
	}
	return nil
}

// FindDescriptor searches discoverd profile for the specified descriptor and UUID
func (p *Profile) FindDescriptor(desc *Descriptor) *Descriptor {
	for _, s := range p.Services {
		for _, c := range s.Characteristics {
			for _, d := range c.Descriptors {
				if d.UUID.Equal(desc.UUID) {
					return d
				}
			}
		}
	}
	return nil
}

// A Service is a BLE service.
type Service struct {
	UUID            UUID
	Characteristics []*Characteristic

	Handle    uint16
	EndHandle uint16
}

// AddCharacteristic adds a characteristic to a service.
// AddCharacteristic panics if the service already contains another characteristic with the same UUID.
func (s *Service) AddCharacteristic(c *Characteristic) *Characteristic {
	for _, x := range s.Characteristics {
		if x.UUID.Equal(c.UUID) {
			panic("service already contains a characteristic with UUID " + c.UUID.String())
		}
	}
	s.Characteristics = append(s.Characteristics, c)
	return c
}

// NewCharacteristic adds a characteristic to a service.
// NewCharacteristic panics if the service already contains another characteristic with the same UUID.
func (s *Service) NewCharacteristic(u UUID) *Characteristic {
	return s.AddCharacteristic(&Characteristic{UUID: u})
}

// A Characteristic is a BLE characteristic.
type Characteristic struct {
	UUID        UUID
	Property    Property
	Secure      Property // FIXME
	Descriptors []*Descriptor
	CCCD        *Descriptor

	Value []byte

	ReadHandler     ReadHandler
	WriteHandler    WriteHandler
	NotifyHandler   NotifyHandler
	IndicateHandler NotifyHandler

	Handle      uint16
	ValueHandle uint16
	EndHandle   uint16
}

// AddDescriptor adds a descriptor to a characteristic.
// AddDescriptor panics if the characteristic already contains another descriptor with the same UUID.
func (c *Characteristic) AddDescriptor(d *Descriptor) *Descriptor {
	for _, x := range c.Descriptors {
		if x.UUID.Equal(d.UUID) {
			panic("service already contains a characteristic with UUID " + d.UUID.String())
		}
	}
	c.Descriptors = append(c.Descriptors, d)
	return d
}

// NewDescriptor adds a descriptor to a characteristic.
// NewDescriptor panics if the characteristic already contains another descriptor with the same UUID.
func (c *Characteristic) NewDescriptor(u UUID) *Descriptor {
	return c.AddDescriptor(&Descriptor{UUID: u})
}

// SetValue makes the characteristic support read requests, and returns a static value.
// SetValue must be called before the containing service is added to a server.
// SetValue panics if the characteristic has been configured with a ReadHandler.
func (c *Characteristic) SetValue(b []byte) {
	if c.ReadHandler != nil {
		panic("charactristic has been configured with a read handler")
	}
	c.Property |= CharRead
	c.Value = make([]byte, len(b))
	copy(c.Value, b)
}

// HandleRead makes the characteristic support read requests, and routes read requests to h.
// HandleRead must be called before the containing service is added to a server.
// HandleRead panics if the characteristic has been configured with a static value.
func (c *Characteristic) HandleRead(h ReadHandler) {
	if c.Value != nil {
		panic("charactristic has been configured with a static value")
	}
	c.Property |= CharRead
	c.ReadHandler = h
}

// HandleWrite makes the characteristic support write and write-no-response requests, and routes write requests to h.
// The WriteHandler does not differentiate between write and write-no-response requests; it is handled automatically.
// HandleWrite must be called before the containing service is added to a server.
func (c *Characteristic) HandleWrite(h WriteHandler) {
	c.Property |= CharWrite | CharWriteNR
	c.WriteHandler = h
}

// HandleNotify makes the characteristic support notify requests, and routes notification requests to h.
// HandleNotify must be called before the containing service is added to a server.
func (c *Characteristic) HandleNotify(h NotifyHandler) {
	c.Property |= CharNotify
	c.NotifyHandler = h
}

// HandleIndicate makes the characteristic support indicate requests, and routes notification requests to h.
// HandleIndicate must be called before the containing service is added to a server.
func (c *Characteristic) HandleIndicate(h NotifyHandler) {
	c.Property |= CharIndicate
	c.IndicateHandler = h
}

// Descriptor is a BLE descriptor
type Descriptor struct {
	UUID     UUID
	Property Property

	Handle uint16
	Value  []byte

	ReadHandler  ReadHandler
	WriteHandler WriteHandler
}

// SetValue makes the descriptor support read requests, and returns a static value.
// SetValue must be called before the containing service is added to a server.
// SetValue panics if the descriptor has already configured with a ReadHandler.
func (d *Descriptor) SetValue(b []byte) {
	if d.ReadHandler != nil {
		panic("descriptor has been configured with a read handler")
	}
	d.Property |= CharRead
	d.Value = make([]byte, len(b))
	copy(d.Value, b)
}

// HandleRead makes the descriptor support read requests, and routes read requests to h.
// HandleRead must be called before the containing service is added to a server.
// HandleRead panics if the descriptor has been configured with a static value.
func (d *Descriptor) HandleRead(h ReadHandler) {
	if d.Value != nil {
		panic("descriptor has been configured with a static value")
	}
	d.Property |= CharRead
	d.ReadHandler = h
}

// HandleWrite makes the descriptor support write and write-no-response requests, and routes write requests to h.
// The WriteHandler does not differentiate between write and write-no-response requests; it is handled automatically.
// HandleWrite must be called before the containing service is added to a server.
func (d *Descriptor) HandleWrite(h WriteHandler) {
	d.Property |= CharWrite | CharWriteNR
	d.WriteHandler = h
}
