/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 main

import (
	"encoding/json"
	"errors"
	"fmt"
	htrace "htrace/client"
	"htrace/common"
	"strings"
	"unicode"
)

// Convert a string into a whitespace-separated sequence of strings.
func tokenize(str string) []string {
	prevQuote := rune(0)
	f := func(c rune) bool {
		switch {
		case c == prevQuote:
			prevQuote = rune(0)
			return true
		case prevQuote != rune(0):
			return false
		case unicode.In(c, unicode.Quotation_Mark):
			prevQuote = c
			return true
		default:
			return unicode.IsSpace(c)
		}
	}
	return strings.FieldsFunc(str, f)
}

// Parses a query string in the format of a series of
// [TYPE] [OPERATOR] [CONST] tuples, joined by AND statements.
type predicateParser struct {
	tokens   []string
	curToken int
}

func (ps *predicateParser) Parse() (*common.Predicate, error) {
	if ps.curToken >= len(ps.tokens) {
		return nil, nil
	}
	if ps.curToken > 0 {
		if strings.ToLower(ps.tokens[ps.curToken]) != "and" {
			return nil, errors.New(fmt.Sprintf("Error parsing on token %d: "+
				"expected predicates to be joined by 'and', but found '%s'",
				ps.curToken, ps.tokens[ps.curToken]))
		}
		ps.curToken++
		if ps.curToken > len(ps.tokens) {
			return nil, errors.New(fmt.Sprintf("Nothing found after 'and' at "+
				"token %d", ps.curToken))
		}
	}
	field := common.Field(strings.ToLower(ps.tokens[ps.curToken]))
	if !field.IsValid() {
		return nil, errors.New(fmt.Sprintf("Invalid field specifier at token %d.  "+
			"Can't understand %s.  Valid field specifiers are %v", ps.curToken,
			ps.tokens[ps.curToken], common.ValidFields()))
	}
	ps.curToken++
	if ps.curToken > len(ps.tokens) {
		return nil, errors.New(fmt.Sprintf("Nothing found after field specifier "+
			"at token %d", ps.curToken))
	}
	op := common.Op(strings.ToLower(ps.tokens[ps.curToken]))
	if !op.IsValid() {
		return nil, errors.New(fmt.Sprintf("Invalid operation specifier at token %d.  "+
			"Can't understand %s.  Valid operation specifiers are %v", ps.curToken,
			ps.tokens[ps.curToken], common.ValidOps()))
	}
	ps.curToken++
	if ps.curToken > len(ps.tokens) {
		return nil, errors.New(fmt.Sprintf("Nothing found after field specifier "+
			"at token %d", ps.curToken))
	}
	val := ps.tokens[ps.curToken]
	ps.curToken++
	return &common.Predicate{Op: op, Field: field, Val: val}, nil
}

func parseQueryString(str string) ([]common.Predicate, error) {
	ps := predicateParser{tokens: tokenize(str)}
	if verbose {
		fmt.Printf("Running query [ ")
		prefix := ""
		for tokenIdx := range ps.tokens {
			fmt.Printf("%s'%s'", prefix, ps.tokens[tokenIdx])
			prefix = ", "
		}
		fmt.Printf(" ]\n")
	}
	preds := make([]common.Predicate, 0)
	for {
		pred, err := ps.Parse()
		if err != nil {
			return nil, err
		}
		if pred == nil {
			break
		}
		preds = append(preds, *pred)
	}
	if len(preds) == 0 {
		return nil, errors.New("Empty query string")
	}
	return preds, nil
}

// Send a query from a query string.
func doQueryFromString(hcl *htrace.Client, str string, lim int) error {
	query := &common.Query{Lim: lim}
	var err error
	query.Predicates, err = parseQueryString(str)
	if err != nil {
		return err
	}
	return doQuery(hcl, query)
}

// Send a query from a raw JSON string.
func doRawQuery(hcl *htrace.Client, str string) error {
	jsonBytes := []byte(str)
	var query common.Query
	err := json.Unmarshal(jsonBytes, &query)
	if err != nil {
		return errors.New(fmt.Sprintf("Error parsing provided JSON: %s\n", err.Error()))
	}
	return doQuery(hcl, &query)
}

// Send a query.
func doQuery(hcl *htrace.Client, query *common.Query) error {
	if verbose {
		qbytes, err := json.Marshal(*query)
		if err != nil {
			qbytes = []byte("marshaling error: " + err.Error())
		}
		fmt.Printf("Sending query: %s\n", string(qbytes))
	}
	spans, err := hcl.Query(query)
	if err != nil {
		return err
	}
	if verbose {
		fmt.Printf("%d results...\n", len(spans))
	}
	for i := range spans {
		fmt.Printf("%s\n", spans[i].ToJson())
	}
	return nil
}
