package evaluator

import (
	"encoding/hex"
	"fmt"
	"net/http"
	"os"
	"os/exec"
	"reflect"
	"strings"

	"database/sql"

	"github.com/buger/jsonparser"
	"github.com/clbanning/mxj"
	"github.com/davecgh/go-spew/spew"
	"github.com/julienschmidt/httprouter"
	"gitlab.com/olanguage/olang/ast"
	"gitlab.com/olanguage/olang/color"
	"gitlab.com/olanguage/olang/filedb"
	"gitlab.com/olanguage/olang/lexer"
	"gitlab.com/olanguage/olang/object"
	"gitlab.com/olanguage/olang/parser"

	_ "github.com/denisenkom/go-mssqldb"
	_ "github.com/go-sql-driver/mysql"
	_ "github.com/lib/pq"
)

var (
	NULL  = &object.Null{}
	TRUE  = &object.Boolean{Value: true}
	FALSE = &object.Boolean{Value: false}
)

func inputEval(input string, env *object.Environment) object.Object {
	l := lexer.New(input)
	p := parser.New(l)
	program := p.ParseProgram()

	return Eval(program, env)
}

func Eval(node ast.Node, env *object.Environment) object.Object {
	switch node := node.(type) {

	case *ast.Program:
		return evalProgram(node, env)

	case *ast.ExpressionStatement:
		return Eval(node.Expression, env)

	case *ast.IntegerLiteral:
		return &object.Integer{Value: node.Value}

	case *ast.FloatLiteral:
		return &object.Float{Value: node.Value}

	case *ast.Boolean:
		return nativeBoolToBooleanObject(node.Value)

	case *ast.PrefixExpression:
		right := Eval(node.Right, env)
		if isError(right) {
			return right
		}
		return evalPrefixExpression(node.Operator, right)

	case *ast.InfixExpression:
		left := Eval(node.Left, env)
		if isError(left) {
			return left
		}

		right := Eval(node.Right, env)
		if isError(right) {
			return right
		}

		return evalInfixExpression(node.Operator, left, right)

	case *ast.BlockStatement:
		return evalBlockStatement(node, env)

	case *ast.IfExpression:
		return evalIfExpression(node, env)

	case *ast.ReturnStatement:
		val := Eval(node.ReturnValue, env)
		if isError(val) {
			return val
		}
		return &object.ReturnValue{Value: val}

	case *ast.LetStatement:
		val := Eval(node.Value, env)
		if isError(val) {
			return val
		}

		env.Set(node.Name.Value, val)

	case *ast.SignalExpression:
		name := node.Name.Value
		value := node.Value
		data, _ := hex.DecodeString(value)
		env.Set(name, &object.Signal{
			Name:  name,
			Value: data,
		})
		return &object.Signal{
			Name:  name,
			Value: data,
		}

	case *ast.Identifier:
		return evalIdentifier(node, env)

	case *ast.ThisExpression:
		return evalThis(node, env)
	case *ast.FunctionLiteral:
		params := node.Parameters
		body := node.Body
		return &object.Function{
			Parameters: params,
			Body:       body,
			Env:        env,
		}
	case *ast.RouteLiteral:
		config := Eval(node.Config, env)
		return &object.Route{
			Config: config,
		}
	case *ast.LoadStatement:
		return evalLoadStatement(node, env)
	case *ast.CustomLiteral:
		name := node.Name
		params := node.Parameters
		body := node.Body
		env.Set(name, &object.Function{
			Parameters: params,
			Body:       body,
			Env:        env,
		})
		return &object.Function{
			Parameters: params,
			Body:       body,
			Env:        env,
		}
	case *ast.ScopeLiteral:
		vars := make([]object.Object, 0)
		for _, stmt := range node.Body.Statements {
			switch lsmt := stmt.(type) {
			case *ast.LetStatement:
				/* get value and set variables arr if not function */
				vars = append(vars, checkType(lsmt.Value))
			}
		}

		env.Set(node.Name, &object.Scope{
			Name: node.Name,
			Body: node.Body,
			Vars: vars,
			Env:  env,
		})
		return &object.Scope{
			Name: node.Name,
			Body: node.Body,
			Vars: vars,
			Env:  env,
		}
	case *ast.ScopeExpression:
		return evalScope(node, env)
	case *ast.ScopeChangeExpression:
		return evalChangeScopeVal(node, env)
	case *ast.LoopExpression:
		return applyLoop(node, env)
	case *ast.WhileExpression:
		return applyWhileLoop(node, env)

	case *ast.ForExpression:
		return applyForLoop(node, env)

	case *ast.CallExpression:
		function := Eval(node.Function, env)
		if isError(function) {
			return function
		}

		args := evalExpressions(node.Arguments, env)
		if len(args) == 1 && isError(args[0]) {
			return args[0]
		}

		return applyFunction(function, args)

	case *ast.StringLiteral:
		return &object.String{Value: node.Value}

	case *ast.ArrayLiteral:
		elements := evalExpressions(node.Elements, env)
		if len(elements) == 1 && isError(elements[0]) {
			return elements[0]
		}

		return &object.Array{Elements: elements}

	case *ast.IndexExpression:
		left := Eval(node.Left, env)
		if isError(left) {
			return left
		}
		index := Eval(node.Index, env)
		if isError(index) {
			return index
		}
		return evalIndexExpression(left, index)

	case *ast.HashLiteral:
		return evalHashLiteral(node, env)

	case *ast.ProcessExpression:
		proc := Eval(node.Process, env).Result()
		definition := node.Definition.TokenLiteral()
		arguments := make([]object.Object, 0)

		procSplit := strings.Split(proc, " ")

		command := procSplit[0]
		args := procSplit[1:]

		for _, arg := range args {
			arguments = append(arguments, &object.String{Value: arg})
		}

		cmd := exec.Command(command, args...)

		output, err := cmd.CombinedOutput()

		errStr := &object.Error{}

		if err != nil {
			return newError("command execute error %s", err)
		}

		env.Set(definition, &object.Process{
			Name:    definition,
			Pid:     int64(cmd.Process.Pid),
			Output:  string(output),
			Command: proc,
			Param:   arguments,
			Error:   errStr,
		})

		return &object.Process{
			Name:    definition,
			Pid:     int64(cmd.Process.Pid),
			Output:  string(output),
			Command: proc,
			Param:   arguments,
			Error:   errStr,
		}
	case *ast.EnvExpression:
		os.Setenv(node.Name, node.Value)
		return &object.SysEnv{
			Name:  node.Name,
			Value: node.Value,
		}
	case *ast.EvalLiteral:
		content := ""
		switch tp := node.Content.(type) {
		case *ast.Identifier:
			content = Eval(tp, env).Result()
		case *ast.StringLiteral:
			content = tp.String()
		}

		return inputEval(content, env)
	case *ast.SocketExpression:
		sock := &object.Socket{
			Addr:     node.Host,
			Port:     node.Port,
			ConType:  node.ConType,
			Debugger: node.Debugger,
			Env:      env,
		}

		env.Set(node.Definition, sock)

		return sock
	case *ast.BeginLiteral:
		obj := &object.Begin{
			Name:         node.Name,
			State:        true,
			ReturnResult: Eval(node.Block, env),
		}
		env.Set(node.Name, obj)
		return Eval(node.Block, env)

	case *ast.AsyncLiteral:
		obj := &object.Async{
			Name:         node.Name,
			ReturnResult: Eval(node.Block, env),
		}
		obj.Channel = make(chan object.Object)

		env.Set(node.Name, obj)

		return Eval(node.Block, env)
	case *ast.WaitExpression:
		asyncObj, _ := env.Get(node.Async)

		switch obj := asyncObj.(type) {
		case *object.Async:
			go func() {
				obj.Channel <- Eval(node.Block, env)
			}()

			obj.ReturnResult = <-obj.Channel

			env.Set(obj.Name, obj)

			return obj
		}

		return asyncObj
	case *ast.ExceptionExpression:
		beginObj, _ := env.Get(node.Begin)

		switch obj := beginObj.(type) {
		case *object.Begin:
			condition := Eval(node.Condition, env)

			if obj.State {
				if isTruthy(condition) {
					obj.State = false
					obj.Error = Eval(node.ErrorBlock, env)
				}
			}

			env.Set(obj.Name, obj)
			return obj
		}
	case *ast.RecoverExpression:
		beginObj, _ := env.Get(node.Begin)
		switch obj := beginObj.(type) {
		case *object.Begin:
			if !obj.State {
				return obj.Error
			}
		}
	case *ast.FinalExpression:
		beginObj, _ := env.Get(node.Begin)
		switch obj := beginObj.(type) {
		case *object.Begin:
			if obj.State {
				return Eval(node.Block, env)
			}
		}
	}
	return nil
}

func evalLoadStatement(load *ast.LoadStatement, env *object.Environment) object.Object {
	var result object.Object

	for _, statement := range load.Statements {
		result = Eval(statement, env)

		if result != nil {
			rt := result.Type()
			if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ {
				return result
			}
		}
	}

	return result
}

func evalProgram(program *ast.Program, env *object.Environment) object.Object {
	var result object.Object

	for _, statement := range program.Statements {
		result = Eval(statement, env)

		switch result := result.(type) {
		case *object.ReturnValue:
			return result.Value
		case *object.Error:
			return result
		}
	}

	return result
}

func evalBlockStatement(block *ast.BlockStatement, env *object.Environment) object.Object {
	var result object.Object

	for _, statement := range block.Statements {
		result = Eval(statement, env)

		if result != nil {
			rt := result.Type()
			if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ {
				return result
			}
		}
	}

	return result
}

func nativeBoolToBooleanObject(input bool) *object.Boolean {
	if input {
		return TRUE
	}

	return FALSE
}

func evalPrefixExpression(operator string, right object.Object) object.Object {
	switch operator {
	case "!":
		return evalBangOperatorExpression(right)
	case "-":
		return evalMinusPrefixOperatorExpression(right)
	default:
		return newError("unknown operator: %s%s", color.Bold(operator), color.Bold(right.Type()))
	}
}

func evalBangOperatorExpression(right object.Object) object.Object {
	switch right {
	case TRUE:
		return FALSE
	case FALSE:
		return TRUE
	case NULL:
		return TRUE
	default:
		return FALSE
	}
}

func evalMinusPrefixOperatorExpression(right object.Object) object.Object {
	if right.Type() != object.INTEGER_OBJ {
		return newError("unknown operator: -%s", color.Bold(right.Type()))
	}

	value := right.(*object.Integer).Value
	return &object.Integer{Value: -value}
}

func evalInfixExpression(operator string, left, right object.Object) object.Object {
	switch {
	case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ:
		return evalIntegerInfixExpression(operator, left, right)
	case left.Type() == object.FLOAT_OBJ && right.Type() == object.FLOAT_OBJ:
		return evalFloatInfixExpression(operator, left, right)
	case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ:
		return evalStringInfixExpression(operator, left, right)
	case operator == "==":
		return nativeBoolToBooleanObject(left == right)
	case operator == "!=":
		return nativeBoolToBooleanObject(left != right)
	case left.Type() != right.Type():
		return newError("type mismatch: %s %s %s", color.Bold(left.Type()), color.Bold(operator), color.Bold(right.Type()))
	default:
		return newError("unknown operator: %s %s %s", color.Bold(left.Type()), color.Bold(operator), color.Bold(right.Type()))
	}
}

func evalIntegerInfixExpression(operator string, left, right object.Object) object.Object {
	leftVal := left.(*object.Integer).Value
	rightVal := right.(*object.Integer).Value

	switch operator {
	case "+":
		return &object.Integer{Value: leftVal + rightVal}
	case "-":
		return &object.Integer{Value: leftVal - rightVal}
	case "*":
		return &object.Integer{Value: leftVal * rightVal}
	case "/":
		return &object.Integer{Value: leftVal / rightVal}
	case "<":
		return nativeBoolToBooleanObject(leftVal < rightVal)
	case ">":
		return nativeBoolToBooleanObject(leftVal > rightVal)
	case "<=":
		return nativeBoolToBooleanObject(leftVal <= rightVal)
	case ">=":
		return nativeBoolToBooleanObject(leftVal >= rightVal)
	case "==":
		return nativeBoolToBooleanObject(leftVal == rightVal)
	case "!=":
		return nativeBoolToBooleanObject(leftVal != rightVal)
	default:
		return newError("unknown operator: %s %s %s", color.Bold(left.Type()), color.Bold(operator), color.Bold(right.Type()))
	}
}

func evalFloatInfixExpression(operator string, left, right object.Object) object.Object {
	leftVal := left.(*object.Float).Value
	rightVal := right.(*object.Float).Value

	switch operator {
	case "+":
		return &object.Float{Value: leftVal + rightVal}
	case "-":
		return &object.Float{Value: leftVal - rightVal}
	case "*":
		return &object.Float{Value: leftVal * rightVal}
	case "/":
		return &object.Float{Value: leftVal / rightVal}
	case "<":
		return nativeBoolToBooleanObject(leftVal < rightVal)
	case ">":
		return nativeBoolToBooleanObject(leftVal > rightVal)
	case "<=":
		return nativeBoolToBooleanObject(leftVal <= rightVal)
	case ">=":
		return nativeBoolToBooleanObject(leftVal >= rightVal)
	case "==":
		return nativeBoolToBooleanObject(leftVal == rightVal)
	case "!=":
		return nativeBoolToBooleanObject(leftVal != rightVal)
	default:
		return newError("unknown operator: %s %s %s", color.Bold(left.Type()), color.Bold(operator), color.Bold(right.Type()))
	}
}

func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Object {
	condition := Eval(ie.Condition, env)

	if isError(condition) {
		return condition
	}

	if isTruthy(condition) {
		return Eval(ie.Consequence, env)
	} else if ie.Alternative != nil {
		return Eval(ie.Alternative, env)
	} else {
		return NULL
	}
}

func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object {
	if val, ok := env.Get(node.Value); ok {
		return val
	}

	if builtin, ok := builtins[node.Value]; ok {
		return builtin
	}

	return newError("definition not found: " + color.Bold(node.Value))
}

func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Object {
	var result []object.Object

	for _, e := range exps {
		evaluated := Eval(e, env)
		if isError(evaluated) {
			return []object.Object{evaluated}
		}
		result = append(result, evaluated)
	}

	return result
}

func evalStringInfixExpression(
	operator string,
	left, right object.Object,
) object.Object {
	if operator == "+" {
		lv := left.(*object.String).Value
		rv := right.(*object.String).Value
		return &object.String{Value: lv + rv}
	}
	if operator == "==" {
		lv := left.(*object.String).Value
		rv := right.(*object.String).Value

		if lv == rv {
			return &object.Boolean{Value: true}
		}

		return &object.Boolean{Value: false}
	}
	return &object.Boolean{Value: false}
}

func evalIndexExpression(left, index object.Object) object.Object {
	switch {
	case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ:
		return evalArrayIndexExpression(left, index)
	case left.Type() == object.HASH_OBJ:
		return evalHashIndexExpression(left, index)
	default:
		return newError("index operator not supported: %s", color.Bold(left.Type()))
	}
}

func evalArrayIndexExpression(array, index object.Object) object.Object {
	arrayObject := array.(*object.Array)
	idx := index.(*object.Integer).Value
	max := int64(len(arrayObject.Elements) - 1)

	if idx < 0 || idx > max {
		return NULL
	}

	return arrayObject.Elements[idx]
}

func evalHashIndexExpression(hash, index object.Object) object.Object {
	hashObj := hash.(*object.Hash)
	k, ok := index.(object.Hashable)
	if !ok {
		return newError("unusable as hash key: %s", color.Bold(index.Type()))
	}

	pair, ok := hashObj.Pairs[k.HashKey()]
	if !ok {
		return NULL
	}

	return pair.Value
}

func evalHashLiteral(
	node *ast.HashLiteral,
	env *object.Environment,
) object.Object {
	pairs := make(map[object.HashKey]object.HashPair)

	for k, v := range node.Pairs {
		key := Eval(k, env)
		if isError(key) {
			return key
		}

		hashKey, ok := key.(object.Hashable)
		if !ok {
			return newError("unusable as hash key: %s", color.Bold(key.Type()))
		}

		value := Eval(v, env)
		if isError(value) {
			return value
		}

		hashed := hashKey.HashKey()
		pairs[hashed] = object.HashPair{Key: key, Value: value}
	}

	return &object.Hash{Pairs: pairs}
}

func isTruthy(obj object.Object) bool {
	switch obj.Result() {
	case "":
		return false
	case "true":
		return true
	case "false":
		return false
	default:
		return true
	}
}

func MakeStruct(typeName string, vals map[string]interface{}) interface{} {
	var sfs []reflect.StructField
	for k, v := range vals {

		t := reflect.TypeOf(v)
		val := reflect.ValueOf(k)

		sf := reflect.StructField{
			Name: strings.Title(fmt.Sprintf("%s", val)),
			Type: t,
		}
		sfs = append(sfs, sf)
	}
	st := reflect.StructOf(sfs)
	so := reflect.New(st)

	return so.Interface()
}

func HashKeyExist(hash *object.Hash, hashpair object.HashPair) bool {
	for _, item := range hash.Pairs {
		if item.Key.Result() == hashpair.Key.Result() {
			return true
		}
	}

	return false
}

func modelCreateSql(model *object.Model, items object.Object) object.Object {
	cols := model.Columns
	db := model.Database.Connection.(*sql.DB)
	crSql := "INSERT INTO " + model.Name + " (%elements%) VALUES (%values%);"
	selPtr := ""
	valPtr := ""

	switch items := items.(type) {
	case *object.Hash:

		for _, item := range items.Pairs {
			switch cols := cols.(type) {
			case *object.Hash:
				v := HashKeyExist(cols, item)
				if !v {
					return newError("%s key not found in %s model", item.Key.Result(), model.Name)
				}
			}
		}
	}

	vals := []interface{}{}

	switch items := items.(type) {
	case *object.Hash:
		lastObjCount := len(items.Pairs) - 1
		i := 0

		for _, pair := range items.Pairs {
			selPtr += pair.Key.Result()
			valPtr += "?"
			vals = append(vals, pair.Value.Result())

			if i != lastObjCount {
				selPtr += ","
				valPtr += ","
			}
			i++
		}
	}

	crSql = strings.Replace(crSql, "%elements%", selPtr, 1)
	crSql = strings.Replace(crSql, "%values%", valPtr, 1)

	stmt, err := db.Prepare(crSql) // error here
	if err != nil {
		return newError("CREATE: %s", err)
	}
	stmt.Exec(vals...)
	defer stmt.Close()

	crSql = "SELECT %select% FROM " + model.Name + " WHERE %where%;"
	selPtr = ""
	wherePtr := ""
	vals = []interface{}{}

	switch items := items.(type) {
	case *object.Hash:
		lastObjCount := len(items.Pairs) - 1
		i := 0
		for _, pair := range items.Pairs {
			wherePtr += pair.Key.Result() + " = ?"
			vals = append(vals, pair.Value.Result())

			if i != lastObjCount {
				wherePtr += " AND "
			}
			i++
		}
	}

	switch cols := cols.(type) {
	case *object.Hash:
		lastObjCount := len(cols.Pairs) - 1
		i := 0
		for _, pair := range cols.Pairs {
			selPtr += pair.Key.Result()

			if i != lastObjCount {
				selPtr += ","
			}

			i++
		}
	}

	crSql = strings.Replace(crSql, "%select%", selPtr, 1)
	crSql = strings.Replace(crSql, "%where%", wherePtr, 1)
	query := &object.String{Value: crSql}
	run := runDbQuery(db, query, vals...)
	elements := run.(*object.Array).Elements

	return elements[len(elements)-1]
}

func modelDeleteSql(model *object.Model, items object.Object) object.Object {
	cols := model.Columns
	db := model.Database.Connection.(*sql.DB)
	crSql := "DELETE FROM " + model.Name + " WHERE %where%;"
	wherePtr := ""

	switch items := items.(type) {
	case *object.Hash:
		for _, item := range items.Pairs {
			switch cols := cols.(type) {
			case *object.Hash:
				v := HashKeyExist(cols, item)
				if !v {
					return newError("%s key not found in %s model", item.Key.Result(), model.Name)
				}
			}
		}
	}

	vals := []interface{}{}

	switch items := items.(type) {
	case *object.Hash:
		lastObjCount := len(items.Pairs) - 1
		i := 0
		for _, pair := range items.Pairs {
			wherePtr += pair.Key.Result() + " = ?"
			vals = append(vals, pair.Value.Result())

			if i != lastObjCount {
				wherePtr += " AND "
			}
			i++
		}
	}

	crSql = strings.Replace(crSql, "%where%", wherePtr, 1)

	stmt, err := db.Prepare(crSql) // error here
	if err != nil {
		return newError("DELETE: %s", err)
	}
	stmt.Exec(vals...)
	defer stmt.Close()

	return &object.Boolean{Value: true}
}

func modelDropSql(model *object.Model) object.Object {
	db := model.Database.Connection.(*sql.DB)
	crSql := "DROP TABLE " + model.Name + ";"

	_, err := db.Exec(crSql) // error here
	if err != nil {
		return newError("DROP: %s", err)
	}

	return &object.Boolean{Value: true}
}

func modelTruncateSql(model *object.Model) object.Object {
	db := model.Database.Connection.(*sql.DB)
	crSql := "TRUNCATE TABLE " + model.Name + ";"

	_, err := db.Exec(crSql) // error here
	if err != nil {
		return newError("TRUNCATE: %s", err)
	}

	return &object.Boolean{Value: true}
}

func modelMigrateSql(model *object.Model) object.Object {
	cols := model.Columns
	db := model.Database.Connection.(*sql.DB)
	crSql := "CREATE TABLE " + model.Name + " (%elements%);"

	switch rowList := cols.(type) {
	case *object.Hash:
		elements := ""
		lastObjCount := len(rowList.Pairs) - 1
		i := 0
		elements += "UUID text,"
		for _, pair := range rowList.Pairs {
			elements += pair.Key.Result() + " \"" + pair.Value.Result() + "\""

			if i != lastObjCount {
				elements += ", "
			}
			i++
		}

		crSql = strings.Replace(crSql, "%elements%", elements, 1)
	}

	_, err := db.Exec(crSql)

	if err != nil {
		return newError("MIGRATE ERROR: %s, QUERY: %s", err, crSql)
	}

	return &object.Boolean{Value: true}
}

func modelFetchSql(model *object.Model, items object.Object) object.Object {
	cols := model.Columns
	db := model.Database.Connection.(*sql.DB)

	crSql := "SELECT %select% FROM " + model.Name + "%where%;"
	selPtr := ""
	wherePtr := ""

	switch cols := cols.(type) {
	case *object.Hash:
		lastObjCount := len(cols.Pairs) - 1
		i := 0
		for _, pair := range cols.Pairs {
			selPtr += pair.Key.Result()

			if i != lastObjCount {
				selPtr += ","
			}

			i++
		}
	}

	vals := []interface{}{}

	switch items := items.(type) {
	case *object.Hash:
		lastObjCount := len(items.Pairs) - 1
		i := 0
		for _, pair := range items.Pairs {
			wherePtr += pair.Key.Result() + " = ?"
			vals = append(vals, pair.Value.Result())

			if i != lastObjCount {
				wherePtr += " AND "
			}
			i++
		}
	}

	crSql = strings.Replace(crSql, "%select%", selPtr, 1)
	if wherePtr == "" {
		crSql = strings.Replace(crSql, "%where%", "", 1)
	} else {
		crSql = strings.Replace(crSql, "%where%", " WHERE "+wherePtr, 1)
	}

	query := &object.String{Value: crSql}
	return runDbQuery(db, query, vals...)
}

func modelUpdateSql(model *object.Model, items object.Object, sets object.Object) object.Object {
	cols := model.Columns
	db := model.Database.Connection.(*sql.DB)
	crSql := "UPDATE " + model.Name + " SET %update% WHERE %where%;"
	updatePtr := ""
	wherePtr := ""

	switch sets := sets.(type) {
	case *object.Hash:
		for _, set := range sets.Pairs {
			switch cols := cols.(type) {
			case *object.Hash:
				v := HashKeyExist(cols, set)
				if !v {
					return newError("%s key not found in %s model", set.Key.Result(), model.Name)
				}
			}
		}
	}

	vals := []interface{}{}

	switch sets := sets.(type) {
	case *object.Hash:
		lastObjCount := len(sets.Pairs) - 1
		i := 0
		for _, pair := range sets.Pairs {
			updatePtr += pair.Key.Result() + " = ?"
			vals = append(vals, pair.Value.Result())

			if i != lastObjCount {
				updatePtr += ", "
			}
			i++
		}
	}

	switch items := items.(type) {
	case *object.Hash:
		lastObjCount := len(items.Pairs) - 1
		i := 0
		for _, pair := range items.Pairs {
			wherePtr += pair.Key.Result() + " = ?"
			vals = append(vals, pair.Value.Result())

			if i != lastObjCount {
				wherePtr += " AND "
			}
			i++
		}
	}

	crSql = strings.Replace(crSql, "%update%", updatePtr, 1)
	crSql = strings.Replace(crSql, "%where%", wherePtr, 1)

	// run
	stmt, err := db.Prepare(crSql) // error here
	if err != nil {
		return newError("UPDATE: %s", err)
	}
	stmt.Exec(vals...)
	defer stmt.Close()

	selPtr := ""

	switch cols := cols.(type) {
	case *object.Hash:
		lastObjCount := len(cols.Pairs) - 1
		i := 0
		for _, pair := range cols.Pairs {
			selPtr += pair.Key.Result()

			if i != lastObjCount {
				selPtr += ","
			}

			i++
		}
	}

	wherePtr = ""
	vals = []interface{}{}

	switch sets := sets.(type) {
	case *object.Hash:
		lastObjCount := len(sets.Pairs) - 1
		i := 0
		for _, pair := range sets.Pairs {
			wherePtr += pair.Key.Result() + " = ?"
			vals = append(vals, pair.Value.Result())

			if i != lastObjCount {
				wherePtr += " AND "
			}
			i++
		}
	}

	crSql = "SELECT %select% FROM " + model.Name + " WHERE %where%;"
	crSql = strings.Replace(crSql, "%where%", wherePtr, 1)
	crSql = strings.Replace(crSql, "%select%", selPtr, 1)

	query := &object.String{Value: crSql}
	return runDbQuery(db, query, vals...)
}

func runPreparedQuery(db *sql.DB, query *object.String, args *object.Array) object.Object {

	vals := []interface{}{}

	for _, arg := range args.Elements {
		vals = append(vals, arg.Result())
	}

	return runDbQuery(db, query, vals...)
}

func runDbQuery(db *sql.DB, query *object.String, args ...interface{}) object.Object {
	rows, err := db.Query(query.Result(), args...)
	if err != nil {
		return newError("%s", err)
	}

	cols, err := rows.Columns()
	if err != nil {
		return newError("%s", err)
	}

	arr := make([]object.Object, 0)
	i := 0
	for rows.Next() {
		columns := make([]interface{}, len(cols))
		columnPointers := make([]interface{}, len(cols))
		for i := range columns {
			columnPointers[i] = &columns[i]
		}

		if err := rows.Scan(columnPointers...); err != nil {
			return newError("%s", err)
		}

		pairs := make(map[object.HashKey]object.HashPair)
		var elm object.Object
		for i, colName := range cols {

			val := columnPointers[i].(*interface{})

			if *val != nil {
				nk := &object.String{Value: string(colName)}
				hashed := nk.HashKey()

				var nv object.Object
				switch ty := reflect.ValueOf(val).Elem().Interface().(type) {
				case int32:
					nv = &object.Integer{Value: int64(ty)}
				case int64:
					nv = &object.Integer{Value: int64(ty)}
				case string:
					nv = &object.String{Value: string(ty)}
				case []byte:
					nv = &object.String{Value: string(ty)}
				case bool:
					nv = &object.Boolean{Value: bool(ty)}
				default:
					nv = &object.Empty{}
				}
				hp := object.HashPair{Key: nk, Value: nv}
				pairs[hashed] = hp
			}
			elm = &object.Hash{Pairs: pairs}
		}

		arr = append(arr, elm)
		i++
	}
	return &object.Array{Elements: arr}
}

func modelCreateInternalDb(model *object.Model, items object.Object) object.Object {
	db := model.Database.Connection.(*filedb.Filedb)

	switch items := items.(type) {
	case *object.Hash:
		for _, item := range items.Pairs {
			db.Store(item.Key.Result(), item.Value.Result())
		}
	}

	return &object.Boolean{Value: true}
}

func modelDeleteInternalDb(model *object.Model, items object.Object) object.Object {
	db := model.Database.Connection.(*filedb.Filedb)

	switch items := items.(type) {
	case *object.Hash:
		for _, item := range items.Pairs {
			db.Delete(item.Key.Result())
		}
	case *object.Array:
		for _, value := range items.Elements {
			db.Delete(value.Result())
		}
	case *object.String:
		db.Delete(items.Result())
	case *object.Integer:
		db.Delete(string(items.Result()))
	}

	return &object.Boolean{Value: true}
}

func modelFetchInternalDb(model *object.Model, items object.Object) object.Object {
	db := model.Database.Connection.(*filedb.Filedb)

	switch items := items.(type) {
	case *object.Hash:
		pairs := make(map[object.HashKey]object.HashPair)
		i := 0
		for _, item := range items.Pairs {
			hashed := object.HashKey{Value: uint64(i)}
			pair := object.HashPair{Key: item.Key, Value: db.Get(item.Key.Result())}
			pairs[hashed] = pair
			i++
		}
		return &object.Hash{Pairs: pairs}
	case *object.Array:
		pairs := make(map[object.HashKey]object.HashPair)
		i := 0
		for _, value := range items.Elements {
			hashed := object.HashKey{Value: uint64(i)}
			pair := object.HashPair{Key: value, Value: db.Get(value.Result())}
			pairs[hashed] = pair
			i++
		}
		return &object.Hash{Pairs: pairs}
	case *object.String:
		return db.Get(items.Result())
	case *object.Integer:
		return db.Get(string(items.Result()))
	}

	return &object.Empty{}
}

func modelUpdateInternalDb(model *object.Model, items object.Object) object.Object {
	db := model.Database.Connection.(*filedb.Filedb)

	switch items := items.(type) {
	case *object.Hash:
		for _, item := range items.Pairs {
			db.Update(item.Key.Result(), item.Key.Result())
		}
	}

	return &object.Boolean{Value: true}
}

func modelSearchInternalDb(model *object.Model, items object.Object) object.Object {
	db := model.Database.Connection.(*filedb.Filedb)

	switch items := items.(type) {
	case *object.Array:
		i := 0
		pairs := make(map[object.HashKey]object.HashPair)
		for _, item := range items.Elements {
			res := db.Find(item.Result())

			for k, v := range res {

				nk := &object.String{Value: k}
				nv := &object.String{Value: v}
				hashed := object.HashKey{Value: uint64(i)}

				hp := object.HashPair{Key: nk, Value: nv}
				pairs[hashed] = hp
				i++
			}

			i++
		}
		return &object.Hash{Pairs: pairs}
	case *object.String:
		res := db.Find(items.Result())
		i := 0
		pairs := make(map[object.HashKey]object.HashPair)
		for k, v := range res {

			nk := &object.String{Value: k}
			nv := &object.String{Value: v}
			hashed := object.HashKey{Value: uint64(i)}

			hp := object.HashPair{Key: nk, Value: nv}
			pairs[hashed] = hp
			i++
		}

		return &object.Hash{Pairs: pairs}
	}
	return &object.Boolean{Value: false}
}

func ParseJsonToString(data object.Object) string {
	result := ""
	switch content := data.(type) {
	case *object.Hash:
		result += `{`
		i := 0
		lastKey := len(content.Pairs) - 1
		for _, pair := range content.Pairs {
			result += ParseJsonToString(pair.Key) + `:` + ParseJsonToString(pair.Value)

			if i != lastKey {
				result += ","
			}
			i++
		}
		result += `}`
	case *object.Array:
		result += `[`
		i := 0
		lastKey := len(content.Elements) - 1
		for _, element := range content.Elements {

			result += ParseJsonToString(element)

			if i != lastKey {
				result += ","
			}

			i++
		}
		result += `]`
	case *object.String:
		result += `"` + content.Result() + `"`
	case *object.Integer:
		result += content.Result()
	case *object.Boolean:
		result += content.Result()
	}

	return result
}

func ParseJson(data []byte) *object.Hash {
	i := 0
	pairs := make(map[object.HashKey]object.HashPair)
	jsonparser.ObjectEach(data, func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error {

		if dataType != jsonparser.String {
			if dataType == jsonparser.Array {
				nk := &object.String{Value: string(key)}
				length := len(value)
				newElements := make([]object.Object, 0, length)
				jsonparser.ArrayEach(value, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
					//fmt.Println(jsonparser.Get(value, "url"))
					newElements = append(newElements, ParseJson(value))
				})
				nv := &object.Array{Elements: newElements}
				hashed := object.HashKey{Value: uint64(i)}

				hp := object.HashPair{Key: nk, Value: nv}
				pairs[hashed] = hp
			}
		} else {
			nk := &object.String{Value: string(key)}
			nv := &object.String{Value: string(value)}
			hashed := object.HashKey{Value: uint64(i)}

			hp := object.HashPair{Key: nk, Value: nv}
			pairs[hashed] = hp
		}

		i++
		return nil
	})
	return &object.Hash{Pairs: pairs}
}

func ParseXML(file string) *object.Hash {
	xmlFile, _ := os.Open(file)
	defer xmlFile.Close()
	m, _ := mxj.NewMapXmlReader(xmlFile)

	leafnodes := m.LeafNodes()
	pairs := make(map[object.HashKey]object.HashPair)
	i := 0
	for _, node := range leafnodes {
		val := node.Value.(string)
		nk := &object.String{Value: string(node.Path)}
		nv := &object.String{Value: string(val)}
		hashed := object.HashKey{Value: uint64(i)}

		hp := object.HashPair{Key: nk, Value: nv}
		pairs[hashed] = hp
		i++
	}
	return &object.Hash{Pairs: pairs}
}

func checkType2(obj object.Object) object.Object {
	switch ty := obj.(type) {
	case *object.String:
		result := &object.String{Value: ty.Value}
		return result
	case *object.Hash:
		result := &object.Hash{Pairs: ty.Pairs}
		return result
	case *object.Array:
		result := &object.Array{Elements: ty.Elements}
		return result
	case *object.Integer:
		result := &object.Integer{Value: ty.Value}
		return result
	case *object.Boolean:
		result := &object.Boolean{Value: ty.Value}
		return result
	default:
		result := &object.Empty{}
		return result
	}
}

func BasicAuth(h httprouter.Handle, requiredUser, requiredPassword string) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		// Get the Basic Authentication credentials
		user, password, hasAuth := r.BasicAuth()

		if hasAuth && user == requiredUser && password == requiredPassword {
			// Delegate request to the given handle
			h(w, r, ps)
		} else {
			// Request Basic Authentication otherwise
			w.Header().Set("WWW-Authenticate", "Basic realm=Restricted")
			http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
		}
	}
}

func newError(format string, a ...interface{}) *object.Error {
	return &object.Error{Message: fmt.Sprintf(format, a...)}
}

func newWarning(format string, a ...interface{}) *object.Warning {
	return &object.Warning{Message: fmt.Sprintf(format, a...)}
}

func DebugInterface(a interface{}) {
	color.Println(fmt.Sprintf("%#v\n", a))
}

func isError(obj object.Object) bool {
	if obj != nil {
		return obj.Type() == object.ERROR_OBJ
	}

	return false
}

func applyLoop(le *ast.LoopExpression, env *object.Environment) object.Object {
	condition := Eval(le.Condition, env)

	if isError(condition) {
		return condition
	}
	if !isTruthy(condition) {
		Eval(le.Consequence, env)
		applyLoop(le, env)
	}

	return &object.Empty{}
}

func applyWhileLoop(le *ast.WhileExpression, env *object.Environment) object.Object {
	condition := Eval(le.Condition, env)

	if isError(condition) {
		return condition
	}
	if isTruthy(condition) {
		Eval(le.Consequence, env)
		applyWhileLoop(le, env)
	}

	return &object.Empty{}
}

func applyForLoop(fe *ast.ForExpression, env *object.Environment) object.Object {
	condition := Eval(fe.Condition, env)
	init, _ := env.Get(fe.Init.(*ast.Identifier).Value)

	if isError(condition) {
		return condition
	}

	if fe.LoopToken.Type == "IN" {
		switch init.(type) {
		case *object.Array:
			for i, elm := range init.(*object.Array).Elements {
				env.Set(fe.Key.(*ast.Identifier).Value, &object.Integer{Value: int64(i)})
				env.Set(fe.Value.(*ast.Identifier).Value, elm)
				Eval(fe.Consequence, env)
			}
		case *object.Hash:
			for _, pairs := range init.(*object.Hash).Pairs {
				key := pairs.Key
				value := pairs.Value

				env.Set(fe.Key.(*ast.Identifier).Value, key)
				env.Set(fe.Value.(*ast.Identifier).Value, value)
				Eval(fe.Consequence, env)
			}
		}
	} else {
		if !isTruthy(condition) {
			Eval(fe.Consequence, env)
			applyForLoop(fe, env)
		}
	}

	return &object.Empty{}
}

func checkType(obj ast.Expression) object.Object {
	switch ty := obj.(type) {
	case *ast.StringLiteral:
		result := &object.String{Value: ty.Value}
		return result
	case *ast.IntegerLiteral:
		result := &object.Integer{Value: ty.Value}
		return result
	case *ast.Boolean:
		result := &object.Boolean{Value: ty.Value}
		return result
	default:
		result := &object.Empty{}
		return result
	}
}

func evalChangeScopeVal(stmt *ast.ScopeChangeExpression, env *object.Environment) object.Object {
	res, _ := env.Get(stmt.Left)
	right := stmt.Right
	val := stmt.Value

	switch topScope := res.(type) {
	case *object.Scope:
		for i := range topScope.Body.Statements {
			switch newStmt := topScope.Body.Statements[i].(type) {
			case *ast.LetStatement:
				lit := newStmt.Name.Token.Literal
				if string(lit) == right {
					topScope.Body.Statements[i] = val
				}
			}

		}
	}
	return &object.Empty{}
}

func evalScope(stmt *ast.ScopeExpression, env *object.Environment) object.Object {
	res, _ := env.Get(stmt.Left)
	right := stmt.Right
	args := stmt.Arguments

	// TODO: Apply all scope vars in every function with scope (this scope only)
	switch topScope := res.(type) {
	case *object.Scope:
		for i := range topScope.Body.Statements {

			switch newStmt := topScope.Body.Statements[i].(type) {
			case *ast.LetStatement:
				lit := newStmt.Name.Token.Literal
				if string(lit) == right {
					switch stmtVal := newStmt.Value.(type) {
					case *ast.FunctionLiteral:

						length := len(args)
						newElements := make([]ast.Expression, 0, length)
						for i := range args {
							newElements = append(newElements, args[i])
						}

						function := Eval(stmtVal, env)
						argList := evalExpressions(newElements, env)
						returnVal := applyFunction(function, argList)

						return returnVal
					default:
						stmtReturnVal := checkType(stmtVal)
						return stmtReturnVal
					}

				}
			}

		}
	}

	return &object.Empty{}
}

func evalThis(this *ast.ThisExpression, env *object.Environment) object.Object {

	spew.Dump(env.GetLastScope())

	return &object.Empty{}
}

func applyFunction(fn object.Object, args []object.Object) object.Object {

	switch fn := fn.(type) {

	case *object.Function:
		if len(fn.Parameters) != len(args) {
			return newError("%s function parameters not match with arguments.\n The values given with the arguments of each function invoked must match.\n The sum of your arguments is %d but the function can define %d values.", fn.Env.GetLastFunctionName(), len(args), len(fn.Parameters))
		}
		extendedEnv := extendFunctionEnv(fn, args)
		evaluated := Eval(fn.Body, extendedEnv)
		return unwrapReturnValue(evaluated)

	case *object.Builtin:
		return fn.Fn(args...)

	default:
		return newError("not a function: %s", color.Bold(fn.Type()))
	}
}

func extendFunctionEnv(fn *object.Function, args []object.Object) *object.Environment {
	env := object.NewEnclosedEnvironment(fn.Env)

	for i, param := range fn.Parameters {
		env.Set(param.Value, args[i])
	}

	return env
}

func unwrapReturnValue(obj object.Object) object.Object {
	if returnValue, ok := obj.(*object.ReturnValue); ok {
		return returnValue.Value
	}

	return obj
}
