package dax

// Directive contains the instructions, sent from the Controller, which a
// compute node is to follow. A Directive is typically JSON-encoded and POSTed
// to a compute node's `/directive` endpoint.
type Directive struct {
	Address Address `json:"address"`

	// Method describes how the compute node should handle the Directive. See
	// the different constants of type DirectiveMethod for how this value is
	// handled.
	Method DirectiveMethod `json:"method"`

	Tables []*QualifiedTable `json:"schema"`

	ComputeRoles   []ComputeRole   `json:"compute-roles"`
	TranslateRoles []TranslateRole `json:"translate-roles"`

	Version uint64 `json:"version"`
}

type DirectiveVersion interface {
	Increment(tx Transaction, delta uint64) (uint64, error)
}

// DirectiveMethod is used to tell the compute node how it should handle the
// Directive.
type DirectiveMethod string

const (
	// DirectiveMethodDiff tells the compute node to diff the Directive with its
	// local, cached Directive and only apply the differences.
	DirectiveMethodDiff DirectiveMethod = "diff"

	// DirectiveMethodReset tells the compute node to delete all of its existing
	// data before applying the directive.
	DirectiveMethodReset DirectiveMethod = "reset"

	// DirectiveMethodSnapshot tells the compute node that the incoming
	// Directive should only contain data version updates related to a snapshot
	// request.
	DirectiveMethodSnapshot DirectiveMethod = "snapshot"
)

// Table returns the ID'd table from the Directive's Tables list. If it's not
// found, it returns nil and a non-nil error. A nil error guarantees that the
// returned table is non-nil.
func (d *Directive) Table(qtid QualifiedTableID) (*QualifiedTable, error) {
	for _, qtbl := range d.Tables {
		// We can't do qtbl.QualifiedID() == qtid because the value of qtid.Name
		// is empty and causes the equality check to fail. Hence the .Equals()
		// method.
		if qtbl.QualifiedID().Equals(qtid) {
			return qtbl, nil
		}
	}
	return nil, NewErrTableIDDoesNotExist(qtid)
}

// ComputeShards returns the list of shards, for the given table, for which this
// compute node is responsible. It assumes that the Directive does not contain
// more than one ComputeRole for the same table; in that case, we would need to
// return the union of Shards.
func (d *Directive) ComputeShards(tbl TableKey) ShardNums {
	if d == nil || d.ComputeRoles == nil {
		return nil
	}

	for _, cr := range d.ComputeRoles {
		if cr.TableKey == tbl {
			return cr.Shards
		}
	}

	return nil
}

// ComputeShardsMap returns a map of table to shards. It assumes that the
// Directive does not contain more than one ComputeRole for the same table; in
// that case, we would need to return the union of Shards.
func (d *Directive) ComputeShardsMap() map[TableKey]ShardNums {
	m := make(map[TableKey]ShardNums)
	if d == nil || d.ComputeRoles == nil {
		return m
	}

	for _, cr := range d.ComputeRoles {
		m[cr.TableKey] = cr.Shards
	}

	return m
}

// TranslatePartitions returns the list of partitions, for the given table, for
// which this translate node is responsible. It assumes that the Directive does
// not contain more than one TranslateRole for the same table; in that case, we
// would need to return the union of Shards.
func (d *Directive) TranslatePartitions(tbl TableKey) PartitionNums {
	if d == nil || d.TranslateRoles == nil {
		return PartitionNums{}
	}

	for _, tr := range d.TranslateRoles {
		if tr.TableKey == tbl {
			return tr.Partitions
		}
	}
	return PartitionNums{}
}

// TranslatePartitionsMap returns a map of table to partitions. It assumes that
// the Directive does not contain more than one TranslateRole for the same
// table; in that case, we would need to return the union of Partitions.
func (d *Directive) TranslatePartitionsMap() map[TableKey]PartitionNums {
	m := make(map[TableKey]PartitionNums)
	if d == nil || d.TranslateRoles == nil {
		return m
	}

	for _, tr := range d.TranslateRoles {
		// Since we added FieldVersions to the TranslateRole, it's possible for
		// a TranslateRole to have an empty Partitions list. In that case, we
		// want to exclude that from the map.
		if len(tr.Partitions) == 0 {
			continue
		}
		m[tr.TableKey] = tr.Partitions
	}

	return m
}

// TranslateFieldsMap returns a map of table to fields. It assumes that
// the Directive does not contain more than one TranslateRole for the same
// table; in that case, we would need to return the union of FieldValues.
func (d *Directive) TranslateFieldsMap() map[TableKey][]FieldName {
	m := make(map[TableKey][]FieldName)
	if d == nil || d.TranslateRoles == nil {
		return m
	}

	for _, tr := range d.TranslateRoles {
		if len(tr.Fields) == 0 {
			continue
		}
		m[tr.TableKey] = tr.Fields
	}

	return m
}

// IsEmpty tells whether a directive is assigning actual responsibilty
// to a node or not. If the directive does not assign responsibility
// for any shard or partition then it is considered empty. This is
// used to determine whether we can ignore an error received from
// applying this directive (an empty directive is often sent to a node
// which is already down).
func (d *Directive) IsEmpty() bool {
	for _, role := range d.ComputeRoles {
		if len(role.Shards) > 0 {
			return false
		}
	}

	for _, role := range d.TranslateRoles {
		if len(role.Partitions) > 0 {
			return false
		}
	}

	return true
}

// Directives is a sortable slice of Directive.
type Directives []*Directive

func (d Directives) Len() int           { return len(d) }
func (d Directives) Less(i, j int) bool { return d[i].Version < d[j].Version }
func (d Directives) Swap(i, j int)      { d[i], d[j] = d[j], d[i] }
