package html

import (
	"context"
	"github.com/MontFerret/ferret/pkg/drivers"
	"github.com/MontFerret/ferret/pkg/runtime/values/types"
	"github.com/pkg/errors"

	"github.com/MontFerret/ferret/pkg/runtime/core"
	"github.com/MontFerret/ferret/pkg/runtime/values"
)

type ParseParams struct {
	drivers.ParseParams
	Driver string
}

// PARSE loads an HTML page from a given string or byte array
// @param params (Object) - Optional, an object containing the following properties :
// 	driver (String) - Optional, driver name.
//      keepCookies (Boolean) - Optional, boolean value indicating whether to use cookies from previous sessions.
//      	i.e. not to open a page in the Incognito mode.
//      cookies (HTTPCookies) - Optional, set of HTTP cookies.
//      headers (HTTPHeaders) - Optional, HTTP headers.
//      viewport (Viewport) - Optional, viewport params.
// @returns (HTMLPage) - Returns parsed and loaded HTML page.
func Parse(ctx context.Context, args ...core.Value) (core.Value, error) {
	if err := core.ValidateArgs(args, 1, 2); err != nil {
		return values.None, err
	}

	arg1 := args[0]

	if err := core.ValidateType(arg1, types.String, types.Binary); err != nil {
		return values.None, err
	}

	var content []byte

	if arg1.Type() == types.String {
		content = []byte(arg1.(values.String))
	} else {
		content = []byte(arg1.(values.Binary))
	}

	var params ParseParams

	if len(args) > 1 {
		if err := core.ValidateType(args[1], types.Object); err != nil {
			return values.None, err
		}

		p, err := parseParseParams(content, args[1].(*values.Object))

		if err != nil {
			return values.None, err
		}

		params = p
	} else {
		params = defaultParseParams(content)
	}

	drv, err := drivers.FromContext(ctx, params.Driver)

	if err != nil {
		return values.None, err
	}

	return drv.Parse(ctx, params.ParseParams)
}

func defaultParseParams(content []byte) ParseParams {
	return ParseParams{
		ParseParams: drivers.ParseParams{
			Content: content,
		},
		Driver: "",
	}
}

func parseParseParams(content []byte, arg *values.Object) (ParseParams, error) {
	res := defaultParseParams(content)

	if arg.Has("driver") {
		driverName := arg.MustGet("driver")

		if err := core.ValidateType(driverName, types.String); err != nil {
			return ParseParams{}, errors.Wrap(err, ".driver")
		}

		res.Driver = driverName.String()
	}

	if arg.Has("keepCookies") {
		keepCookies := arg.MustGet("keepCookies")

		if err := core.ValidateType(keepCookies, types.Boolean); err != nil {
			return ParseParams{}, errors.Wrap(err, ".keepCookies")
		}

		res.KeepCookies = bool(keepCookies.(values.Boolean))
	}

	if arg.Has("cookies") {
		cookies := arg.MustGet("cookies")

		if err := core.ValidateType(cookies, types.Array, types.Object); err != nil {
			return res, err
		}

		switch c := cookies.(type) {
		case *values.Array:
			cookies, err := parseCookieArray(c)

			if err != nil {
				return ParseParams{}, errors.Wrap(err, ".cookies")
			}

			res.Cookies = cookies
		case *values.Object:
			cookies, err := parseCookieObject(c)

			if err != nil {
				return ParseParams{}, errors.Wrap(err, ".cookies")
			}

			res.Cookies = cookies
		default:
			res.Cookies = make(drivers.HTTPCookies)
		}
	}

	if arg.Has("headers") {
		headers := arg.MustGet("headers")

		if err := core.ValidateType(headers, types.Object); err != nil {
			return ParseParams{}, errors.Wrap(err, ".headers")
		}

		res.Headers = parseHeader(headers.(*values.Object))
	}

	if arg.Has("viewport") {
		viewport, err := parseViewport(arg.MustGet("viewport"))

		if err != nil {
			return ParseParams{}, errors.Wrap(err, ".viewport")
		}

		res.Viewport = viewport
	}

	return res, nil
}