package rdfs

import (
	"fmt"
	"github.com/go-fed/activity/astool/rdf"
	"strings"
)

const (
	rdfsSpecURI       = "http://www.w3.org/2000/01/rdf-schema#"
	commentSpec       = "comment"
	domainSpec        = "domain"
	isDefinedBySpec   = "isDefinedBy"
	rangeSpec         = "range"
	subClassOfSpec    = "subClassOf"
	subPropertyOfSpec = "subPropertyOf"
)

// RDFSchemaOntology is the Ontology for rdfs.
type RDFSchemaOntology struct{}

// SpecURI returns the URI for the RDFS spec.
func (o *RDFSchemaOntology) SpecURI() string {
	return rdfsSpecURI
}

// Load this Ontology without an alias.
func (o *RDFSchemaOntology) Load() ([]rdf.RDFNode, error) {
	return o.LoadAsAlias("")
}

// LoadAsAlias loads this ontology with an alias.
func (o *RDFSchemaOntology) LoadAsAlias(s string) ([]rdf.RDFNode, error) {
	return []rdf.RDFNode{
		&rdf.AliasedDelegate{
			Spec:     rdfsSpecURI,
			Alias:    s,
			Name:     commentSpec,
			Delegate: &comment{},
		},
		&rdf.AliasedDelegate{
			Spec:     rdfsSpecURI,
			Alias:    s,
			Name:     domainSpec,
			Delegate: &domain{},
		},
		&rdf.AliasedDelegate{
			Spec:     rdfsSpecURI,
			Alias:    s,
			Name:     isDefinedBySpec,
			Delegate: &isDefinedBy{},
		},
		&rdf.AliasedDelegate{
			Spec:     rdfsSpecURI,
			Alias:    s,
			Name:     rangeSpec,
			Delegate: &ranges{},
		},
		&rdf.AliasedDelegate{
			Spec:     rdfsSpecURI,
			Alias:    s,
			Name:     subClassOfSpec,
			Delegate: &subClassOf{},
		},
		&rdf.AliasedDelegate{
			Spec:     rdfsSpecURI,
			Alias:    s,
			Name:     subPropertyOfSpec,
			Delegate: &subPropertyOf{},
		},
	}, nil
}

// LoadSpecificAsAlias loads a specific node as an alias.
func (o *RDFSchemaOntology) LoadSpecificAsAlias(alias, name string) ([]rdf.RDFNode, error) {
	switch name {
	case commentSpec:
		return []rdf.RDFNode{
			&rdf.AliasedDelegate{
				Spec:     "",
				Alias:    "",
				Name:     alias,
				Delegate: &comment{},
			},
		}, nil
	case domainSpec:
		return []rdf.RDFNode{
			&rdf.AliasedDelegate{
				Spec:     "",
				Alias:    "",
				Name:     alias,
				Delegate: &domain{},
			},
		}, nil
	case isDefinedBySpec:
		return []rdf.RDFNode{
			&rdf.AliasedDelegate{
				Spec:     "",
				Alias:    "",
				Name:     alias,
				Delegate: &isDefinedBy{},
			},
		}, nil
	case rangeSpec:
		return []rdf.RDFNode{
			&rdf.AliasedDelegate{
				Spec:     "",
				Alias:    "",
				Name:     alias,
				Delegate: &ranges{},
			},
		}, nil
	case subClassOfSpec:
		return []rdf.RDFNode{
			&rdf.AliasedDelegate{
				Spec:     "",
				Alias:    "",
				Name:     alias,
				Delegate: &subClassOf{},
			},
		}, nil
	case subPropertyOfSpec:
		return []rdf.RDFNode{
			&rdf.AliasedDelegate{
				Spec:     "",
				Alias:    "",
				Name:     alias,
				Delegate: &subPropertyOf{},
			},
		}, nil
	}
	return nil, fmt.Errorf("rdfs ontology cannot find %q to alias to %q", name, alias)
}

// LoadElement does nothing.
func (o *RDFSchemaOntology) LoadElement(name string, payload map[string]interface{}) ([]rdf.RDFNode, error) {
	return nil, nil
}

// GetByName returns a bare node by name.
func (o *RDFSchemaOntology) GetByName(name string) (rdf.RDFNode, error) {
	name = strings.TrimPrefix(name, o.SpecURI())
	switch name {
	case commentSpec:
		return &comment{}, nil
	case domainSpec:
		return &domain{}, nil
	case isDefinedBySpec:
		return &isDefinedBy{}, nil
	case rangeSpec:
		return &ranges{}, nil
	case subClassOfSpec:
		return &subClassOf{}, nil
	case subPropertyOfSpec:
		return &subPropertyOf{}, nil
	}
	return nil, fmt.Errorf("rdfs ontology could not find node for name %s", name)
}

var _ rdf.RDFNode = &comment{}

// comment sets Notes on vocabulary items.
type comment struct{}

// Enter returns an error.
func (n *comment) Enter(key string, ctx *rdf.ParsingContext) (bool, error) {
	return true, fmt.Errorf("rdfs comment cannot be entered")
}

// Exit returns an error.
func (n *comment) Exit(key string, ctx *rdf.ParsingContext) (bool, error) {
	return true, fmt.Errorf("rdfs comment cannot be exited")
}

// Apply sets the string value on Current's Notes.
//
// Returns an error if value isn't a string or Current can't set Notes.
func (n *comment) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) {
	note, ok := value.(string)
	if !ok {
		return true, fmt.Errorf("rdf comment not given string value")
	}
	if ctx.Current == nil {
		return true, fmt.Errorf("rdf comment given nil Current")
	}
	noteSetter, ok := ctx.Current.(rdf.NotesSetter)
	if !ok {
		return true, fmt.Errorf("rdf comment not given NotesSetter")
	}
	noteSetter.SetNotes(note)
	return true, nil
}

var _ rdf.RDFNode = &domain{}

// domain is rdfs:domain.
type domain struct{}

// Enter Pushes a Reference as Current.
func (d *domain) Enter(key string, ctx *rdf.ParsingContext) (bool, error) {
	ctx.Push()
	ctx.Current = make([]rdf.VocabularyReference, 0)
	return true, nil
}

// Exit Pops a slice of References and sets it on the parent Property.
//
// Returns an error if the popped item is not a slice of References, or if the
// Current after Popping is not a Property.
func (d *domain) Exit(key string, ctx *rdf.ParsingContext) (bool, error) {
	i := ctx.Current
	ctx.Pop()
	vr, ok := i.([]rdf.VocabularyReference)
	if !ok {
		return true, fmt.Errorf("rdfs domain exit did not get []rdf.VocabularyReference")
	}
	vp, ok := ctx.Current.(*rdf.VocabularyProperty)
	if !ok {
		return true, fmt.Errorf("rdf domain exit Current is not *rdf.VocabularyProperty")
	}
	vp.Domain = append(vp.Domain, vr...)
	return true, nil
}

// Apply returns an error.
func (d *domain) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) {
	return true, fmt.Errorf("rdfs domain cannot be applied")
}

var _ rdf.RDFNode = &isDefinedBy{}

// isDefinedBy is rdfs:isDefinedBy.
type isDefinedBy struct{}

// Enter returns an error.
func (i *isDefinedBy) Enter(key string, ctx *rdf.ParsingContext) (bool, error) {
	return true, fmt.Errorf("rdfs isDefinedBy cannot be entered")
}

// Exit returns an error.
func (i *isDefinedBy) Exit(key string, ctx *rdf.ParsingContext) (bool, error) {
	return true, fmt.Errorf("rdfs isDefinedBy cannot be exited")
}

// Apply sets the string value as Current's URI.
//
// Returns an error if value is not a string or if Current cannot have a URI
// set.
func (i *isDefinedBy) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) {
	s, ok := value.(string)
	if !ok {
		return true, fmt.Errorf("rdfs isDefinedBy given non-string: %T", value)
	}
	u, ok := ctx.Current.(rdf.URISetter)
	if !ok {
		return true, fmt.Errorf("rdfs isDefinedBy Current is not rdf.URISetter: %T", ctx.Current)
	}
	return true, u.SetURI(s)
}

var _ rdf.RDFNode = &ranges{}

// ranges is rdfs:ranges.
type ranges struct{}

// Enter Pushes as a slice of References as Current.
func (r *ranges) Enter(key string, ctx *rdf.ParsingContext) (bool, error) {
	ctx.Push()
	ctx.Current = make([]rdf.VocabularyReference, 0)
	return true, nil
}

// Exit Pops a slice of References and sets it on the parent Property.
//
// Returns an error if the popped item is not a slice of references, or if the
// Current item after popping is not a Property.
func (r *ranges) Exit(key string, ctx *rdf.ParsingContext) (bool, error) {
	i := ctx.Current
	ctx.Pop()
	vr, ok := i.([]rdf.VocabularyReference)
	if !ok {
		return true, fmt.Errorf("rdfs ranges exit did not get []rdf.VocabularyReference")
	}
	vp, ok := ctx.Current.(*rdf.VocabularyProperty)
	if !ok {
		return true, fmt.Errorf("rdf ranges exit Current is not *rdf.VocabularyProperty")
	}
	vp.Range = append(vp.Range, vr...)
	return true, nil
}

// Apply returns an error.
func (r *ranges) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) {
	return true, fmt.Errorf("rdfs ranges cannot be applied")
}

var _ rdf.RDFNode = &subClassOf{}

// subClassOf implements rdfs:subClassOf.
type subClassOf struct{}

// Enter Pushes a Reference as Current.
func (s *subClassOf) Enter(key string, ctx *rdf.ParsingContext) (bool, error) {
	ctx.Push()
	ctx.Current = &rdf.VocabularyReference{}
	return true, nil
}

// Exit Pops a Reference and appends it to the parent Type's Extends.
//
// Returns an error if the popped item is not a reference, or if the Current
// item after popping is not a Type.
func (s *subClassOf) Exit(key string, ctx *rdf.ParsingContext) (bool, error) {
	i := ctx.Current
	ctx.Pop()
	vr, ok := i.(*rdf.VocabularyReference)
	if !ok {
		return true, fmt.Errorf("rdfs subclassof exit did not get *rdf.VocabularyReference")
	}
	vt, ok := ctx.Current.(*rdf.VocabularyType)
	if !ok {
		return true, fmt.Errorf("rdf subclassof exit Current is not *rdf.VocabularyType")
	}
	vt.Extends = append(vt.Extends, *vr)
	return true, nil
}

// Apply returns an error.
func (s *subClassOf) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) {
	return true, fmt.Errorf("rdfs subclassof cannot be applied")
}

var _ rdf.RDFNode = &subPropertyOf{}

// subPropertyOf is rdfs:subPropertyOf
type subPropertyOf struct{}

// Enter Pushes a Reference as Current.
func (s *subPropertyOf) Enter(key string, ctx *rdf.ParsingContext) (bool, error) {
	ctx.Push()
	ctx.Current = &rdf.VocabularyReference{}
	return true, nil
}

// Exit Pops a Reference and sets it as the parent property's SubpropertyOf.
//
// Returns an error if the popped item is not a Reference, or if after popping
// Current is not a Property.
func (s *subPropertyOf) Exit(key string, ctx *rdf.ParsingContext) (bool, error) {
	i := ctx.Current
	ctx.Pop()
	vr, ok := i.(*rdf.VocabularyReference)
	if !ok {
		return true, fmt.Errorf("rdfs subpropertyof exit did not get *rdf.VocabularyReference")
	}
	vp, ok := ctx.Current.(*rdf.VocabularyProperty)
	if !ok {
		return true, fmt.Errorf("rdf subpropertyof exit Current is not *rdf.VocabularyProperty")
	}
	vp.SubpropertyOf = *vr
	return true, nil
}

// Apply returns an error.
func (s *subPropertyOf) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) {
	return true, fmt.Errorf("rdfs subpropertyof cannot be applied")
}
