// Copyright 2014 The Cayley Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package query defines the graph session interface general to all query languages.
package query

import (
	"context"
	"errors"
	"fmt"
	"io"

	"github.com/cayleygraph/cayley/graph"
)

var ErrParseMore = errors.New("query: more input required")

type ErrUnsupportedCollation struct {
	Collation Collation
}

func (e *ErrUnsupportedCollation) Error() string {
	return fmt.Sprintf("unsupported collation: %v", e.Collation)
}

// Iterator for query results.
type Iterator interface {
	// Next advances the iterator to the next value, which will then be available through
	// the Result method. It returns false if no further advancement is possible, or if an
	// error was encountered during iteration.  Err should be consulted to distinguish
	// between the two cases.
	Next(ctx context.Context) bool
	// Results returns the current result. The type depends on the collation mode of the query.
	Result() interface{}
	// Err returns any error that was encountered by the Iterator.
	Err() error
	// Close the iterator and do internal cleanup.
	Close() error
}

// Collation of results.
type Collation int

const (
	// Raw collates results as maps or arrays of graph.Refs or any other query-native or graph-native data type.
	Raw = Collation(iota)
	// REPL collates results as strings which will be used in CLI.
	REPL = Collation(iota)
	// JSON collates results as maps, arrays and values, that can be encoded to JSON.
	JSON
	// JSONLD collates results as maps, arrays and values compatible with JSON-LD spec.
	JSONLD
)

// Options for the query execution.
type Options struct {
	Limit     int
	Collation Collation
}

type Session interface {
	// Execute runs the query and returns an iterator over the results.
	// Type of results depends on Collation. See Options for details.
	Execute(ctx context.Context, query string, opt Options) (Iterator, error)
}

type REPLSession = Session

// ResponseWriter is a subset of http.ResponseWriter
type ResponseWriter interface {
	Write([]byte) (int, error)
	WriteHeader(int)
}

// Language is a description of query language.
type Language struct {
	Name    string
	Session func(graph.QuadStore) Session

	// Custom HTTP handlers

	HTTPQuery func(ctx context.Context, qs graph.QuadStore, w ResponseWriter, r io.Reader)
	HTTPError func(w ResponseWriter, err error)
}

var languages = make(map[string]Language)

// RegisterLanguage register a new query language.
func RegisterLanguage(lang Language) {
	languages[lang.Name] = lang
}

// NewSession creates a new session for specified query language.
// It returns nil if language was not registered.
func NewSession(qs graph.QuadStore, lang string) Session {
	if l := languages[lang]; l.Session != nil {
		return l.Session(qs)
	}
	return nil
}

// GetLanguage returns a query language description.
// It returns nil if language was not registered.
func GetLanguage(lang string) *Language {
	l, ok := languages[lang]
	if ok {
		return &l
	}
	return nil
}

// Languages returns names of registered query languages.
func Languages() []string {
	out := make([]string, 0, len(languages))
	for name := range languages {
		out = append(out, name)
	}
	return out
}

// Execute runs the query in a given language and returns an iterator over the results.
// Type of results depends on Collation. See Options for details.
func Execute(ctx context.Context, qs graph.QuadStore, lang, query string, opt Options) (Iterator, error) {
	l := GetLanguage(lang)
	if l == nil {
		return nil, fmt.Errorf("unsupported language: %q", lang)
	}
	sess := l.Session(qs)
	return sess.Execute(ctx, query, opt)
}
