package request

import (
	"reflect"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awsutil"
)

//type Paginater interface {
//	HasNextPage() bool
//	NextPage() *Request
//	EachPage(fn func(data interface{}, isLastPage bool) (shouldContinue bool)) error
//}

// HasNextPage returns true if this request has more pages of data available.
func (r *Request) HasNextPage() bool {
	return len(r.nextPageTokens()) > 0
}

// nextPageTokens returns the tokens to use when asking for the next page of
// data.
func (r *Request) nextPageTokens() []interface{} {
	if r.Operation.Paginator == nil {
		return nil
	}

	if r.Operation.TruncationToken != "" {
		tr, _ := awsutil.ValuesAtPath(r.Data, r.Operation.TruncationToken)
		if len(tr) == 0 {
			return nil
		}

		switch v := tr[0].(type) {
		case *bool:
			if !aws.BoolValue(v) {
				return nil
			}
		case bool:
			if v == false {
				return nil
			}
		}
	}

	tokens := []interface{}{}
	tokenAdded := false
	for _, outToken := range r.Operation.OutputTokens {
		v, _ := awsutil.ValuesAtPath(r.Data, outToken)
		if len(v) > 0 {
			tokens = append(tokens, v[0])
			tokenAdded = true
		} else {
			tokens = append(tokens, nil)
		}
	}
	if !tokenAdded {
		return nil
	}

	return tokens
}

// NextPage returns a new Request that can be executed to return the next
// page of result data. Call .Send() on this request to execute it.
func (r *Request) NextPage() *Request {
	tokens := r.nextPageTokens()
	if len(tokens) == 0 {
		return nil
	}

	data := reflect.New(reflect.TypeOf(r.Data).Elem()).Interface()
	nr := New(r.Config, r.ClientInfo, r.Handlers, r.Retryer, r.Operation, awsutil.CopyOf(r.Params), data)
	for i, intok := range nr.Operation.InputTokens {
		awsutil.SetValueAtPath(nr.Params, intok, tokens[i])
	}
	return nr
}

// EachPage iterates over each page of a paginated request object. The fn
// parameter should be a function with the following sample signature:
//
//   func(page *T, lastPage bool) bool {
//       return true // return false to stop iterating
//   }
//
// Where "T" is the structure type matching the output structure of the given
// operation. For example, a request object generated by
// DynamoDB.ListTablesRequest() would expect to see dynamodb.ListTablesOutput
// as the structure "T". The lastPage value represents whether the page is
// the last page of data or not. The return value of this function should
// return true to keep iterating or false to stop.
func (r *Request) EachPage(fn func(data interface{}, isLastPage bool) (shouldContinue bool)) error {
	for page := r; page != nil; page = page.NextPage() {
		if err := page.Send(); err != nil {
			return err
		}
		if getNextPage := fn(page.Data, !page.HasNextPage()); !getNextPage {
			return page.Error
		}
	}

	return nil
}
