package evaluator

import (
	"bufio"
	"crypto/md5"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"math"
	"math/rand"
	"net"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
	"syscall"
	"time"

	_ "unsafe"

	"github.com/skratchdot/open-golang/open"

	stub "github.com/oytunistrator/asm/generator/unsafe-stub"

	"gitlab.com/olanguage/olang/color"
	"gitlab.com/olanguage/olang/crypto"
	"gitlab.com/olanguage/olang/filedb"
	"gitlab.com/olanguage/olang/object"
	"gitlab.com/olanguage/olang/systemlib"
	"gitlab.com/olanguage/olang/watcher"

	"database/sql"

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

	_ "github.com/mattn/go-sqlite3"
)

var ApplyFunctionInside func(fn object.Object, args []object.Object) object.Object

func init() {
	ApplyFunctionInside = applyFunction
}

var builtins = map[string]*object.Builtin{
	"itostr": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 1 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}

			switch arg := args[0].(type) {
			case *object.Integer:
				return &object.String{Value: string(arg.Result())}
			default:
				return newError("argument to `len` not supported, got %s", args[0].Type())
			}
		},
	},
	"len": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 1 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}

			switch arg := args[0].(type) {
			case *object.Array:
				return &object.Integer{Value: int64(len(arg.Elements))}
			case *object.String:
				return &object.Integer{Value: int64(len(arg.Value))}
			case *object.Integer:
				return &object.Integer{Value: int64(len(strconv.FormatInt(arg.Value, 10)))}
			default:
				return newError("argument to `len` not supported, got %s", args[0].Type())
			}
		},
	},

	"first": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 1 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}

			if args[0].Type() != object.ARRAY_OBJ {
				return newError("argument to `first` must be ARRAY, got %s", args[0].Type())
			}

			arr := args[0].(*object.Array)
			if len(arr.Elements) > 0 {
				return arr.Elements[0]
			}

			return &object.Empty{}
		},
	},

	"last": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 1 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}

			if args[0].Type() != object.ARRAY_OBJ {
				return newError("argument to `last` must be ARRAY, got %s", args[0].Type())
			}

			arr := args[0].(*object.Array)
			length := len(arr.Elements)
			if length > 0 {
				return arr.Elements[length-1]
			}

			return &object.Empty{}
		},
	},

	"rest": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 1 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}

			if args[0].Type() != object.ARRAY_OBJ {
				return newError("argument to `rest` must be ARRAY, got %s", args[0].Type())
			}

			arr := args[0].(*object.Array)
			length := len(arr.Elements)
			if length > 0 {
				newElements := make([]object.Object, length-1)
				copy(newElements, arr.Elements[1:length])
				return &object.Array{Elements: newElements}
			}

			return &object.Empty{}
		},
	},

	"push": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) < 2 {
				return newError("wrong number of arguments. got=%d, want=2 or 3", len(args))
			}

			switch obj := args[0].(type) {
			case *object.Array:
				length := len(obj.Elements)

				newElements := make([]object.Object, length+1)
				copy(newElements, obj.Elements)
				newElements[length] = args[1]

				return &object.Array{Elements: newElements}
			case *object.Hash:
				if len(args) == 3 {
					length := len(obj.Pairs)
					replaceKey := false
					var replaceId object.HashKey

					pairs := make(map[object.HashKey]object.HashPair)
					i := 0
					for _, item := range obj.Pairs {
						hashed := object.HashKey{Value: uint64(i)}
						pair := object.HashPair{Key: item.Key, Value: item.Value}
						pairs[hashed] = pair
						if item.Key.Result() == args[1].Result() {
							replaceKey = true
							replaceId = hashed
						}
						i++
					}

					if replaceKey {
						hashed := replaceId
						pairs[hashed] = object.HashPair{Key: args[1], Value: args[2]}
					} else {
						hashed := object.HashKey{Value: uint64(length)}
						pairs[hashed] = object.HashPair{Key: args[1], Value: args[2]}
					}

					return &object.Hash{Pairs: pairs}
				}
			default:
				return newError("argument to `push` must be ARRAY or HASH, got %s", args[0].Type())
			}
			return &object.Empty{}
		},
	},
	"colorize": {
		Fn: func(args ...object.Object) object.Object {

			if len(args) <= 1 {
				return newError("wrong number of arguments. got=%d, want=2 or more", len(args))
			}

			text := ""
			switch arg := args[0].(type) {
			case *object.String:
				text = arg.Value
			default:
				return newError("argument to `len` not supported, got %s", args[0].Type())
			}

			colorArg := ""
			switch arg := args[1].(type) {
			case *object.String:
				colorArg = arg.Value
			default:
				return newError("argument to `len` not supported, got %s", args[1].Type())
			}

			backArg := false
			if len(args) == 3 {
				switch arg := args[2].(type) {
				case *object.Boolean:
					backArg = arg.Value
				}
			}

			createdColor := color.Create(
				colorArg,
				text,
				backArg)

			return &object.String{Value: string(createdColor.StdOut())}
		},
	},
	"webserver": {
		Fn: func(args ...object.Object) object.Object {
			webServer := NewServer("webserver")
			return webServer.Server(args...)
		},
	},
	"read": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) <= 0 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}
			path := ""
			switch arg := args[0].(type) {
			case *object.String:
				path = string(arg.Value)
			default:
				return newError("argument to `read` not supported, got %s", args[0].Type())
			}
			read := systemlib.ReadFile(path)

			return &object.String{Value: read}
		},
	},
	"write": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 2 {
				return newError("wrong number of arguments. got=%d, want=2", len(args))
			}
			path := ""
			content := ""
			write := false
			switch arg := args[0].(type) {
			case *object.String:
				path = arg.Result()
			default:
				return newError("argument to `write` not supported, got %s", args[0].Type())
			}

			switch arg := args[1].(type) {
			case *object.String:
				content = arg.Result()
			default:
				return newError("argument to `write` not supported, got %s", args[0].Type())
			}

			systemlib.CreateFile(path)
			write = systemlib.WriteFile(path, content)

			return &object.Boolean{Value: write}
		},
	},
	"remove": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 1 {
				return newError("wrong number of arguments. got=%d, want=2", len(args))
			}

			path := ""

			switch arg := args[0].(type) {
			case *object.String:
				path = arg.Result()
			default:
				return newError("argument to `remove` not supported, got %s", args[0].Type())
			}

			del := systemlib.DeleteFile(path)

			return &object.Boolean{Value: del}
		},
	},
	"mkdir": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 1 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}

			switch arg := args[0].(type) {
			case *object.String:
				systemlib.CreateDir(arg.Result())
			case *object.Array:
				for _, elm := range arg.Elements {
					systemlib.CreateDir(elm.Result())
				}
			default:
				return newError("argument to `mkdir` not supported, got %s", args[0].Type())
			}

			return &object.Empty{}
		},
	},
	"rmdir": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 1 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}

			switch arg := args[0].(type) {
			case *object.String:
				systemlib.DeleteDir(arg.Result())

			case *object.Array:
				for _, elm := range arg.Elements {
					systemlib.DeleteDir(elm.Result())
				}
			default:
				return newError("argument to `rmdir` not supported, got %s", args[0].Type())
			}

			return &object.Empty{}
		},
	},
	"finfo": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 1 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}

			pairs := make(map[object.HashKey]object.HashPair)

			switch arg := args[0].(type) {
			case *object.String:
				finfo, err := os.Stat(arg.Result())

				if err != nil {
					return newError("finfo error: %s", err.Error())
				}

				hkey := object.HashKey{Value: uint64(0)}
				hval := object.HashPair{Key: &object.String{Value: "name"}, Value: &object.String{Value: finfo.Name()}}

				pairs[hkey] = hval

				hkey = object.HashKey{Value: uint64(1)}
				hval = object.HashPair{Key: &object.String{Value: "size"}, Value: &object.Integer{Value: finfo.Size()}}

				pairs[hkey] = hval

				hkey = object.HashKey{Value: uint64(2)}
				hval = object.HashPair{Key: &object.String{Value: "isdir"}, Value: &object.Boolean{Value: finfo.IsDir()}}

				pairs[hkey] = hval

				hkey = object.HashKey{Value: uint64(3)}
				hval = object.HashPair{Key: &object.String{Value: "perm"}, Value: &object.Integer{Value: int64(finfo.Mode().Perm())}}

				pairs[hkey] = hval

				hkey = object.HashKey{Value: uint64(4)}
				hval = object.HashPair{Key: &object.String{Value: "date"}, Value: &object.String{Value: string(finfo.ModTime().String())}}

				pairs[hkey] = hval

			default:
				return newError("argument to `finfo` not supported, got %s", args[0].Type())
			}

			return &object.Hash{Pairs: pairs}
		},
	},
	"chmod": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 2 {
				return newError("wrong number of arguments. got=%d, want=2", len(args))
			}

			switch arg := args[0].(type) {
			case *object.String:
				file := arg.Result()
				switch mod := args[1].(type) {
				case *object.Integer:
					unixMod := os.FileMode(mod.Value)
					os.Chmod(file, unixMod)
				default:
					return newError("argument to `chmod` not supported, got %s", args[1].Type())
				}
			default:
				return newError("argument to `chmod` not supported, got %s", args[0].Type())
			}

			return &object.Boolean{Value: true}
		},
	},
	"atostr": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) <= 0 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}
			result := ""
			switch arg := args[0].(type) {
			case *object.Array:
				for _, arg := range arg.Elements {
					result += string(arg.Result())
					if len(args) == 2 {
						switch arg := args[1].(type) {
						case *object.String:
							result += string(arg.Result())
						default:
							return newError("argument to `atostr` not supported, got %s", args[1].Type())
						}
					}
				}
			default:
				return newError("argument to `atostr` not supported, got %s", args[0].Type())
			}

			return &object.String{Value: result}
		},
	},
	"json": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) <= 0 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}

			result := ParseJsonToString(args[0])
			return &object.String{Value: result}
		},
	},
	"rand": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) < 1 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}

			rand.Seed(time.Now().UnixNano())
			num := 0
			str := ""

			switch arg := args[0].(type) {
			case *object.Integer:
				num = int(arg.Value) + 1
			default:
				return newError("argument to `rand` not supported, got %s", args[0].Type())
			}

			result := int64(rand.Intn(num))

			if len(args) == 2 {

				switch arg := args[1].(type) {
				case *object.String:
					str = arg.Result()
				default:
					return newError("argument to `rand` not supported, got %s", args[0].Type())
				}

				var letterRunes = []rune(str)
				b := make([]rune, num)
				for i := range b {
					b[i] = letterRunes[rand.Intn(len(letterRunes))]
				}

				return &object.String{Value: string(b)}
			}

			return &object.Integer{Value: result}
		},
	},

	"getval": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) > 1 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}
			switch arg := args[0].(type) {
			case *object.Hash:
				hash := arg
				switch arg := args[1].(type) {
				case *object.String:
					for _, pair := range hash.Pairs {
						if pair.Key.Inspect() == arg.Inspect() {
							return checkType2(pair.Value)
						}
					}
				}
			case *object.Array:
				arr := arg
				switch arg := args[1].(type) {
				case *object.Integer:
					for _, arr := range arr.Elements {
						if arr.Inspect() == arg.Inspect() {
							return checkType2(arr)
						}
					}
				}
			default:
				return newError("argument to `getval` not supported, got %s", args[0].Type())
			}

			return &object.Boolean{Value: true}
		},
	},
	"jsonf": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) > 1 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}
			switch arg := args[0].(type) {
			case *object.String:
				file := systemlib.ReadFile(arg.Value)

				data := []byte(file)

				return ParseJson(data)
			default:
				return newError("argument to `jsonp` not supported, got %s", args[0].Type())
			}
		},
	},
	"jsonp": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) > 1 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}
			switch arg := args[0].(type) {
			case *object.String:
				data := []byte(arg.Value)

				return ParseJson(data)
			default:
				return newError("argument to `jsonp` not supported, got %s", args[0].Type())
			}
		},
	},
	"xmlf": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) > 1 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}
			switch arg := args[0].(type) {
			case *object.String:
				file := systemlib.ReadFile(arg.Value)
				return ParseXML(file)
			default:
				return newError("argument to `xmlp` not supported, got %s", args[0].Type())
			}
		},
	},
	"xmlp": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) > 1 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}
			switch arg := args[0].(type) {
			case *object.String:
				return ParseXML(arg.Value)
			default:
				return newError("argument to `xmlp` not supported, got %s", args[0].Type())
			}
		},
	},
	"wdir": {
		Fn: func(args ...object.Object) object.Object {
			res, _ := os.Getwd()
			return &object.String{Value: res}
		},
	},
	"chdir": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) > 1 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}
			switch arg := args[0].(type) {
			case *object.String:
				os.Chdir(string(arg.Value))
				res, _ := os.Getwd()
				return &object.String{Value: res}
			default:
				return newError("argument to `chdir` not supported, got %s", args[0].Type())
			}
		},
	},
	"args": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) > 1 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}
			switch arg := args[0].(type) {
			case *object.Integer:
				for i, argument := range os.Args {
					if arg.Value == int64(i) {
						return &object.String{Value: argument}
					}
				}
			default:
				return newError("argument to `args` not supported, got %s", args[0].Type())
			}
			return &object.String{Value: ""}
		},
	},
	"exist": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) > 1 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}
			switch arg := args[0].(type) {
			case *object.String:
				_, err := os.Stat(arg.Value)

				if os.IsNotExist(err) {
					return &object.Boolean{Value: false}
				}

				if err != nil {
					return &object.Boolean{Value: false}
				}

			default:
				return newError("argument to `exist` not supported, got %s", args[0].Type())
			}
			return &object.Boolean{Value: true}
		},
	},
	"renderf": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 2 {
				return newError("wrong number of arguments. got=%d, want=2", len(args))
			}
			switch arg := args[0].(type) {
			case *object.String:
				file := systemlib.ReadFile(arg.Value)
				switch arg := args[1].(type) {
				case *object.Hash:
					for _, pair := range arg.Pairs {
						file = strings.Replace(file, "{% "+pair.Key.Result()+" %}", pair.Value.Result(), -1)
					}
					return &object.String{Value: file}
				default:
					return newError("argument to `renderf` not supported, got %s", args[0].Type())
				}
			default:
				return newError("argument to `renderf` not supported, got %s", args[0].Type())
			}
		},
	},
	"sysenv": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 0 {
				switch envKey := args[0].(type) {
				case *object.String:
					if len(args) == 2 {
						switch envVal := args[1].(type) {
						case *object.String:
							os.Setenv(string(envKey.Value), string(envVal.Value))
						default:
							return newError("argument to `sysenv` not supported, got %s", args[0].Type())
						}
					}
					return &object.String{Value: os.Getenv(string(envKey.Value))}
				default:
					return newError("argument to `sysenv` not supported, got %s", args[0].Type())
				}
			} else {
				i := 0
				pairs := make(map[object.HashKey]object.HashPair)
				for _, e := range os.Environ() {
					pair := strings.Split(e, "=")

					nk := &object.String{Value: string(pair[0])}
					nv := &object.String{Value: string(pair[1])}
					hashed := object.HashKey{Value: uint64(i)}

					hp := object.HashPair{Key: nk, Value: nv}
					pairs[hashed] = hp
					i++
				}
				return &object.Hash{Pairs: pairs}
			}
		},
	},
	"proc_kill": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 1 {
				return newError("`proc_kill` function needs 1 you defined only %s arguments.", len(args))
			}
			switch cmd := args[0].(type) {
			case *object.Process:
				cmd.Proc.Process.Kill()
			default:
				return newError("argument to `proc_kill` not supported, got %s", args[0].Type())
			}

			return &object.Empty{}
		},
	},
	"open": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) < 1 {
				return newError("`open` function needs 1 you defined only %s arguments.", len(args))
			}

			command := ""
			switch arg := args[0].(type) {
			case *object.String:
				command = arg.Value
			default:
				return newError("argument to `open` not supported, got %s", arg.Type())
			}

			attr := ""
			if len(args) > 1 {
				for i := 1; i < len(args); i++ {
					switch ar := args[i].(type) {
					case *object.String:
						attr += ar.Result()
					default:
						return newError("argument to `exec` not supported, got %s", ar.Type())
					}
				}
			}

			open.RunWith(attr, command)

			return &object.Empty{}
		},
	},
	"run": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) < 1 {
				return newError("`run` function needs 1 you defined only %s arguments.", len(args))
			}

			command := ""
			switch arg := args[0].(type) {
			case *object.String:
				command = arg.Value
			default:
				return newError("argument to `run` not supported, got %s", arg.Type())
			}

			var attr []string
			if len(args) > 1 {
				for i := 1; i < len(args); i++ {
					switch ar := args[i].(type) {
					case *object.String:
						attr = append(attr, ar.Result())
					default:
						return newError("argument to `run` not supported, got %s", ar.Type())
					}
				}
			}

			binary, lookErr := exec.LookPath(command)
			if lookErr != nil {
				return newError("command not found error %s", lookErr)
			}

			env := os.Environ()

			execErr := syscall.Exec(binary, attr, env)
			if execErr != nil {
				return newError("error %s", execErr)
			}

			return &object.Empty{}
		},
	},
	"exec": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) < 1 {
				return newError("`exec` function needs 1 you defined only %s arguments.", len(args))
			}

			command := ""
			switch arg := args[0].(type) {
			case *object.String:
				command = arg.Value
			default:
				return newError("argument to `exec` not supported, got %s", arg.Type())
			}

			attr := ""
			if len(args) > 1 {
				for i := 1; i < len(args); i++ {
					switch ar := args[i].(type) {
					case *object.String:
						attr += ar.Result()
					default:
						return newError("argument to `exec` not supported, got %s", ar.Type())
					}
				}
			}

			cmd := exec.Command(command, attr)

			output, err := cmd.CombinedOutput()

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

			return &object.Process{Pid: int64(cmd.Process.Pid), Output: string(output), Proc: cmd}
		},
	},
	"sh_exec": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) < 1 {
				return newError("`sh_exec` function needs 1 you defined only %s arguments.", len(args))
			}

			command := ""
			switch arg := args[0].(type) {
			case *object.String:
				command = arg.Value
			default:
				return newError("argument to `sh_exec` not supported, got %s", arg.Type())
			}

			attr := ""
			if len(args) > 1 {
				for i := 1; i < len(args); i++ {
					switch ar := args[i].(type) {
					case *object.String:
						attr += ar.Result()
					default:
						return newError("argument to `sh_exec` not supported, got %s", ar.Type())
					}
				}
			}

			cmd := exec.Command("sh", "-c", "`"+command+" "+attr+"`")

			output, err := cmd.CombinedOutput()

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

			return &object.Process{Pid: int64(cmd.Process.Pid), Output: string(output), Proc: cmd}
		},
	},
	"cmd_exec": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) < 1 {
				return newError("`cmd_exec` function needs 1 you defined only %s arguments.", len(args))
			}

			command := ""
			switch arg := args[0].(type) {
			case *object.String:
				command = arg.Value
			default:
				return newError("argument to `cmd_exec` not supported, got %s", arg.Type())
			}

			attr := ""
			if len(args) > 1 {
				for i := 1; i < len(args); i++ {
					switch ar := args[i].(type) {
					case *object.String:
						attr += ar.Result()
					default:
						return newError("argument to `cmd_exec` not supported, got %s", ar.Type())
					}
				}
			}

			cmd := exec.Command("cmd.exe", "/c", "`"+command+" "+attr+"`")

			output, err := cmd.CombinedOutput()

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

			return &object.Process{Pid: int64(cmd.Process.Pid), Output: string(output), Proc: cmd}
		},
	},
	"proc_out": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) < 1 {
				return newError("`proc_out` function needs 1 you defined only %s arguments.", len(args))
			}

			results := make([]object.Object, 0)
			for _, arg := range args {
				switch ar := arg.(type) {
				case *object.Process:
					results = append(results, &object.String{Value: ar.Output})
				default:
					return newError("argument to `proc_out` not supported, got %s", ar.Type())
				}
			}
			return &object.Array{Elements: results}
		},
	},
	"proc_pid": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) < 1 {
				return newError("`proc_pid` function needs 1 you defined only %s arguments.", len(args))
			}

			results := make([]object.Object, 0)
			for _, arg := range args {
				switch ar := arg.(type) {
				case *object.Process:
					results = append(results, &object.Integer{Value: ar.Pid})
				default:
					return newError("argument to `proc_pid` not supported, got %s", ar.Type())
				}
			}
			return &object.Array{Elements: results}
		},
	},
	"inspect": {
		Fn: func(args ...object.Object) object.Object {
			for _, arg := range args {
				switch ar := arg.(type) {
				default:
					if ar != nil {
						fmt.Println(ar.Inspect())
					}
				}
			}
			return &object.Empty{}
		},
	},
	"regexp_check": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 2 {
				return newError("regexp_check function needs 2 you defined only %s arguments.", len(args))
			}

			switch matchstr := args[0].(type) {
			case *object.String:
				rex := regexp.MustCompile(matchstr.Value)
				switch text := args[1].(type) {
				case *object.String:
					arr := rex.MatchString(text.Value)
					return &object.Boolean{Value: arr}
				default:
					return newError("argument to `regexp_check` not supported, got %s", text.Type())
				}
			default:
				return newError("argument to `regexp_check` not supported, got %s", matchstr.Type())
			}
		},
	},
	"regexp_find": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 2 {
				return newError("regexp_find function needs 2 you defined only %s arguments.", len(args))
			}

			switch matchstr := args[0].(type) {
			case *object.String:
				rex := regexp.MustCompile(matchstr.Value)
				switch text := args[1].(type) {
				case *object.String:
					arr := rex.FindAllString(text.Value, -2)
					resultElements := make([]object.Object, 0, 1)
					for _, result := range arr {
						_res_str := &object.String{Value: result}
						resultElements = append(resultElements, _res_str)
					}
					return &object.Array{Elements: resultElements}
				default:
					return newError("argument to `regexp_find` not supported, got %s", text.Type())
				}
			default:
				return newError("argument to `regexp_find` not supported, got %s", matchstr.Type())
			}
		},
	},
	"sock_send": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 2 {
				return newError("sock_send function needs 2 you defined only %s arguments.", len(args))
			}

			switch arg := args[0].(type) {
			case *object.Socket:
				result := ""
				sock := arg

				switch msg := args[1].(type) {
				case *object.String:
					switch sock.ConType {
					case "tcp":
						addr := strings.Join([]string{sock.Addr, sock.Port}, ":")
						conn, err := net.Dial(sock.ConType, addr)

						defer conn.Close()

						if err != nil {
							log.Fatalln(err)
						}

						conn.Write([]byte(msg.Result()))
						buff := make([]byte, 1024)
						n, _ := conn.Read(buff)
						result = fmt.Sprintf("%s", buff[:n])
					case "udp":
						port, _ := strconv.Atoi(sock.Port)
						addr := net.UDPAddr{
							Port: port,
							IP:   net.ParseIP(sock.Addr),
						}

						conn, err := net.DialUDP("udp", nil, &addr)

						defer conn.Close()

						if err != nil {
							log.Fatalln(err)
						}

						conn.Write([]byte(msg.Result()))
						buff := make([]byte, 1024)
						n, _, err := conn.ReadFromUDP(buff)
						if err != nil {
							result = ""
						}
						result = fmt.Sprintf("%s", buff[:n])

					}

					return &object.String{Value: result}
				default:
					return newError("argument to `dial_send` not supported, got %s", arg.Type())
				}
			default:
				return newError("argument to `dial_send` not supported, got %s", arg.Type())
			}
		},
	},
	"sock_listen": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) > 6 {
				return newError("sock_listen function needs 2 you defined only %s arguments.", len(args))
			}

			switch sock := args[0].(type) {
			case *object.Socket:

				switch list := args[1].(type) {
				case *object.Hash:
					if len(args) > 2 {
						sock.DefaultResponse = args[2]
					}
					if len(args) == 6 {
						sock.OnReady = args[3].Result()
						sock.OnRequest = args[4].Result()
						sock.OnResponse = args[5].Result()
					}
					ml := make([]object.Message, 0)

					for _, pair := range list.Pairs {
						ml = append(ml, object.Message{Request: pair.Key.Result(), Response: pair.Value.Result()})
					}

					sock.Messages = ml

					switch sock.ConType {
					case "tcp":
						addr := strings.Join([]string{sock.Addr, sock.Port}, ":")
						listen, err := net.Listen(sock.ConType, addr)

						if listen == nil {
							return nil
						}
						defer listen.Close()
						if err != nil {
							os.Exit(1)
						}

						if sock.OnReady != "" {
							log.Println(strings.Replace(sock.OnReady, "{{addr}}", addr, -1))
						}

						for {
							conn, err := listen.Accept()
							if err != nil {
								continue
							}
							request := make([]byte, 1024) // set maximum request length to 128B to prevent flood based attacks
							defer conn.Close()

							for {
								read_len, err := conn.Read(request)
								if err != nil {
									break
								}

								request := string(request[:read_len])

								response := sock.FindResponse(request)

								if response == "" {
									switch defaultResponse := sock.DefaultResponse.(type) {
									case *object.Function:
										args := []object.Object{&object.String{Value: request}, &object.String{Value: response}}
										response = ApplyFunctionInside(defaultResponse, args).Result()
									default:
										response = sock.DefaultResponse.Result()
									}
								}

								conn.Write([]byte(response))

								if sock.Debugger {
									log.Println(conn.LocalAddr().String() + " < " + request)
									log.Println(conn.LocalAddr().String() + " > " + response)
								}
							}
						}
					case "udp":
						port, _ := strconv.Atoi(sock.Port)
						addr := net.UDPAddr{
							Port: port,
							IP:   net.ParseIP(sock.Addr),
						}

						listen, err := net.ListenUDP("udp", &addr)

						if listen == nil {
							return nil
						}
						defer listen.Close()
						if err != nil {
							os.Exit(1)
						}

						if sock.OnReady != "" {
							log.Println(strings.Replace(sock.OnReady, "{{addr}}", sock.Addr, -1))
						}

						for {
							request := make([]byte, 1024)
							for {
								n, addr, err := listen.ReadFromUDP(request[:])
								if err != nil {
									break
								}

								request := string(request[:n])

								response := sock.FindResponse(request)

								if response == "" {
									switch defaultResponse := sock.DefaultResponse.(type) {
									case *object.Function:
										args := []object.Object{&object.String{Value: request}, &object.String{Value: response}}
										response = ApplyFunctionInside(defaultResponse, args).Result()
									default:
										response = sock.DefaultResponse.Result()
									}
								}

								listen.WriteToUDP([]byte(response), addr)

								if sock.Debugger {
									log.Println(addr.IP.String() + " < " + request)
									log.Println(addr.IP.String() + " > " + response)
								}
							}
						}
					}

				default:
					return newError("argument to `sock_listen` not supported, got %s", sock.Type())
				}
			default:
				return newError("argument to `sock_listen` not supported, got %s", sock.Type())
			}

			return &object.Empty{}
		},
	},

	"quit": {
		Fn: func(args ...object.Object) object.Object {
			os.Exit(0)
			return &object.Empty{}
		},
	},
	"exit": {
		Fn: func(args ...object.Object) object.Object {
			os.Exit(0)
			return &object.Empty{}
		},
	},
	"database": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 2 {
				return newError("database function needs 2 you defined only %s arguments.", len(args))
			}

			obj := &object.Database{}

			switch typeStr := args[0].(type) {
			case *object.String:
				obj.DbType = typeStr.Result()
			default:
				return newError("argument to `database` not supported, got %s", typeStr.Type())
			}

			switch conStr := args[1].(type) {
			case *object.String:
				obj.ConnString = conStr.Result()
			default:
				return newError("argument to `database` not supported, got %s", conStr.Type())
			}

			switch obj.DbType {
			case "postgres", "mysql", "sqlite3":
				db, err := sql.Open(obj.DbType, obj.ConnString)
				if err != nil {
					return newError("Connection failed.")
				} else {
					obj.Connection = db
				}
			case "internal":
				filename := string(obj.ConnString)
				fdb := filedb.New(filename)
				fdb.Create()
				obj.Connection = fdb
			default:
				warning := newWarning("O Language is not supported %s database type. Switched internal database mode. Sorry :(", obj.DbType)
				color.Println(warning.Result())
				filename := string(obj.ConnString)
				fdb := filedb.New(filename)
				fdb.Create()
				obj.Connection = fdb
			}

			return obj
		},
	},
	"model": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 3 {
				return newError("model function needs 3 you defined only %s arguments.", len(args))
			}

			modelReturn := &object.Model{}

			switch db := args[0].(type) {
			case *object.Database:
				modelReturn.Database = db
			default:
				return newError("argument to `model` not supported, got %s", db.Type())
			}

			switch name := args[1].(type) {
			case *object.String:
				modelReturn.Name = name.Result()
			default:
				return newError("argument to `model` not supported, got %s", name.Type())
			}

			switch defm := args[2].(type) {
			case *object.Hash:
				modelReturn.Columns = defm
			default:
				return newError("argument to `model` not supported, got %s", defm.Type())
			}

			return modelReturn
		},
	},
	"migrate": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 1 {
				return newError("model function minimum needs 1 model. you defined only %s model.", len(args))
			}

			for _, arg := range args {
				switch model := arg.(type) {
				case *object.Model:
					switch model.Database.DbType {
					case "postgres", "mysql", "sqlite3":
						return modelMigrateSql(model)
					case "internal":
						return newError("%s doesnt support migrate function.", model.Database.DbType)
					default:
						return newError("internal doesnt support migrate function.")
					}

				}
			}

			return &object.Empty{}
		},
	},
	"fetch": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 2 {
				return newError("fetch function needs 2 you defined only %s arguments.", len(args))
			}

			switch model := args[0].(type) {
			case *object.Model:
				switch model.Database.DbType {
				case "postgres", "mysql", "sqlite3":
					return modelFetchSql(model, args[1])
				case "internal":
					return modelFetchInternalDb(model, args[1])
				default:
					return modelFetchInternalDb(model, args[1])
				}

			}

			return &object.Empty{}
		},
	},
	"update": {
		Fn: func(args ...object.Object) object.Object {

			if len(args) > 2 {
				return newError("update function needs 2 or more you defined only %s arguments.", len(args))
			}

			switch model := args[0].(type) {
			case *object.Model:
				switch model.Database.DbType {
				case "postgres", "mysql", "sqlite3":
					return modelUpdateSql(model, args[1], args[2])
				case "internal":
					return modelUpdateInternalDb(model, args[1])
				default:
					return modelUpdateInternalDb(model, args[1])
				}

			}

			return &object.Empty{}
		},
	},
	"create": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 2 {
				return newError("create function needs 2 you defined only %s arguments.", len(args))
			}

			switch model := args[0].(type) {
			case *object.Model:
				switch model.Database.DbType {
				case "postgres", "mysql", "sqlite3":
					return modelCreateSql(model, args[1])
				case "internal":
					return modelCreateInternalDb(model, args[1])
				default:
					return modelCreateInternalDb(model, args[1])
				}

			}

			return &object.Empty{}
		},
	},
	"delete": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 2 {
				return newError("create function needs 2 you defined only %s arguments.", len(args))
			}

			switch model := args[0].(type) {
			case *object.Model:
				switch model.Database.DbType {
				case "postgres", "mysql", "sqlite3":
					return modelDeleteSql(model, args[1])
				case "internal":
					return modelDeleteInternalDb(model, args[1])
				default:
					return modelDeleteInternalDb(model, args[1])
				}

			}

			return &object.Empty{}
		},
	},
	"drop": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 1 {
				return newError("drop function needs 1 you defined only %s arguments.", len(args))
			}

			switch model := args[0].(type) {
			case *object.Model:
				switch model.Database.DbType {
				case "postgres", "mysql", "sqlite3":
					return modelDropSql(model)
				case "internal":
					return newError("%s doesnt support drop function.", model.Database.DbType)
				default:
					return newError("internal doesnt support drop function.")
				}

			}

			return &object.Empty{}
		},
	},
	"truncate": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 1 {
				return newError("create function needs 1 you defined only %s arguments.", len(args))
			}

			switch model := args[0].(type) {
			case *object.Model:
				switch model.Database.DbType {
				case "postgres", "mysql", "sqlite3":
					return modelTruncateSql(model)
				case "internal":
					return newError("%s doesnt support truncate function.", model.Database.DbType)
				default:
					return newError("internal doesnt support truncate function.")
				}
			}

			return &object.Empty{}
		},
	},
	"query": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) < 2 {
				return newError("query function needs minimum 2 you defined only %s arguments.", len(args))
			}

			switch model := args[0].(type) {
			case *object.Model:
				switch model.Database.DbType {
				case "postgres", "mysql", "sqlite3":
					//cols := model.Columns
					db := model.Database.Connection.(*sql.DB)
					switch query := args[1].(type) {
					case *object.String:
						if len(args) == 3 {
							switch arg := args[2].(type) {
							case *object.Array:
								return runPreparedQuery(db, query, arg)
							case *object.String:
								return runDbQuery(db, query, arg.Result())
							case *object.Integer:
								return runDbQuery(db, query, arg.Result())
							}
						}
						return runDbQuery(db, query)
					}
				case "internal":
					return newError("%s doesnt support query function.", model.Database.DbType)
				default:
					return newError("internal doesnt support query function.")
				}
			}

			return &object.Empty{}
		},
	},
	"search": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 2 {
				return newError("search function needs 2 you defined only %s arguments.", len(args))
			}

			switch model := args[0].(type) {
			case *object.Model:
				switch model.Database.DbType {
				case "postgres", "mysql", "sqlite3":
					return newError("%s doesnt support truncate function.", model.Database.DbType)
				case "internal":
					return modelSearchInternalDb(model, args[1])
				default:
					return modelSearchInternalDb(model, args[1])
				}

			}

			return &object.Empty{}
		},
	},
	"replace": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 3 {
				return newError("replace function needs 3 you defined only %s arguments.", len(args))
			}

			str := ""

			switch source := args[0].(type) {
			case *object.String:

				switch find := args[1].(type) {
				case *object.String:
					switch change := args[2].(type) {
					case *object.String:
						str = strings.Replace(source.Result(), find.Result(), change.Result(), -1)

						return &object.String{Value: str}
					default:
						return newError("argument to `replace` not supported, got %s", change.Type())
					}
				default:
					return newError("argument to `replace` not supported, got %s", find.Type())
				}
			default:
				return newError("argument to `replace` not supported, got %s", source.Type())
			}
		},
	},
	"asm": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) < 2 {
				return newError("asm function needs 2 or more. you defined only %s arguments.", len(args))
			}

			switch opCode := args[0].(type) {
			case *object.String:
				switch dest := args[1].(type) {
				case *object.String, *object.Integer, *object.Null:
					//dst = dest.Result()

					var newArgs []interface{}
					if len(args) > 2 {
						for i := 2; i < (len(args) - 2); i++ {
							c := (i - 2)
							newArgs[c] = args[i].Result()
						}
					}
					stub.Asm(opCode.Result(), dest.Result(), newArgs...)
					return &object.Boolean{Value: true}
				default:
					return newError("destination code must be string or integer, got %s", dest.Type())
				}
			default:
				return newError("operation code must be string, got %s", opCode.Type())
			}
		},
	},
	"math_sum": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 3 {
				return newError("math_sum() 3 arguments needed, got %s", len(args))
			}

			result := int64(0)

			switch start := args[0].(type) {
			case *object.Integer:
				switch end := args[1].(type) {
				case *object.Integer:
					switch op := args[2].(type) {
					case *object.Array:

						length := len(op.Elements)

						newElements := make([]object.Object, 0, length)
						for _, elm := range op.Elements {
							switch a := elm.(type) {
							case *object.Integer:
								for i := start.Value; i <= end.Value; i++ {
									result = result + (int64(i) * a.Value)
								}
								newElements = append(newElements, &object.Integer{Value: int64(result)})
							}
						}
						return &object.Array{Elements: newElements}
					case *object.Function:
						if len(op.Parameters) == 2 {
							args := make([]object.Object, 0, 2)
							args = append(args, start)
							args = append(args, end)
							return ApplyFunctionInside(op, args)
						}
					default:
						for i := start.Value; i <= end.Value; i++ {
							result = result + i
						}
						return &object.Integer{Value: int64(result)}
					}
				default:
					return newError("math_sum() seccond variable is must be int, your definition is %s", end.Type())
				}
			default:
				return newError("math_sum() first variable is must be int, your definition is %s", start.Type())
			}
			return &object.Empty{}
		},
	},
	"map": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) < 2 {
				return newError("map() function need minimum 2 value, got %s", len(args))
			}

			if len(args) == 2 {
				switch fun := args[0].(type) {
				case *object.Function:
					switch ob := args[1].(type) {
					case *object.Array:
						length := len(ob.Elements)

						newElements := make([]object.Object, 0, length)
						for _, elm := range ob.Elements {
							switch a := elm.(type) {
							case *object.Integer:
								args := make([]object.Object, 0, 1)
								args = append(args, a)
								newElements = append(newElements, ApplyFunctionInside(fun, args))
							}
						}
						return &object.Array{Elements: newElements}
					case *object.Hash:
						length := len(ob.Pairs)
						newElements := make([]object.Object, 0, length)
						for _, pair := range ob.Pairs {
							args := make([]object.Object, 0, 2)
							args = append(args, pair.Key)
							args = append(args, pair.Value)
							newElements = append(newElements, ApplyFunctionInside(fun, args))
						}
						return &object.Array{Elements: newElements}
					default:
						return newError("map() second variable is must be array or hash, your definition is %s", ob.Type())
					}
				default:
					return newError("map() first variable is must be function, needs one or two parameters, your definition is %s", fun.Type())
				}
			}

			if len(args) > 2 {
				switch fun := args[0].(type) {
				case *object.Function:
					totalArgs := len(args)
					dropedFunc := make([]object.Object, totalArgs-1)
					copy(dropedFunc, args[1:totalArgs])

					return ApplyFunctionInside(fun, dropedFunc)
				default:
					return newError("map() first variable is must be function, needs some parameters, your definition is %s", fun.Type())
				}
			}

			return &object.Empty{}
		},
	},
	"math_cos": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 1 {
				return newError("math_cos() function need one argument, got %s", len(args))
			}

			switch num := args[0].(type) {
			case *object.Integer:
				result := math.Cos(float64(num.Value))
				return &object.Float{Value: result}
			case *object.Float:
				result := math.Cos(num.Value)
				return &object.Float{Value: result}
			default:
				return newError("math_cos() first variable is must be integer, your definition is %s", num.Type())
			}
		},
	},
	"math_sin": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 1 {
				return newError("math_sin() function need one argument, got %s", len(args))
			}

			switch num := args[0].(type) {
			case *object.Integer:
				result := math.Sin(float64(num.Value))
				return &object.Float{Value: result}
			case *object.Float:
				result := math.Sin(num.Value)
				return &object.Float{Value: result}
			default:
				return newError("math_sin() first variable is must be integer, your definition is %s", num.Type())
			}
		},
	},
	"math_tan": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 1 {
				return newError("math_tan() function need one argument, got %s", len(args))
			}

			switch num := args[0].(type) {
			case *object.Integer:
				result := math.Tan(float64(num.Value))
				return &object.Float{Value: result}
			case *object.Float:
				result := math.Tan(num.Value)
				return &object.Float{Value: result}
			default:
				return newError("math_tan() first variable is must be integer, your definition is %s", num.Type())
			}
		},
	},
	"math_log": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 1 {
				return newError("math_log() function need one argument, got %s", len(args))
			}

			switch num := args[0].(type) {
			case *object.Integer:
				result := math.Log(float64(num.Value))
				return &object.Float{Value: result}
			case *object.Float:
				result := math.Log(num.Value)
				return &object.Float{Value: result}
			default:
				return newError("math_log() first variable is must be integer, your definition is %s", num.Type())
			}
		},
	},
	"math_abs": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) != 1 {
				return newError("math_abs() function need one argument, got %s", len(args))
			}

			switch num := args[0].(type) {
			case *object.Integer:
				result := math.Abs(float64(num.Value))
				return &object.Float{Value: result}
			case *object.Float:
				result := math.Abs(num.Value)
				return &object.Float{Value: result}
			default:
				return newError("math_abs() first variable is must be integer, your definition is %s", num.Type())
			}
		},
	},

	"output": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) <= 0 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}

			result := &object.String{}

			switch str := args[0].(type) {
			case *object.String:
				length := len(args)
				newArgs := make([]object.Object, length-1)
				copy(newArgs, args[1:length])
				convertObj := make([]interface{}, 0)
				for i := 0; i < len(newArgs); i++ {
					convertObj = append(convertObj, newArgs[i].Result())
				}
				result.Value = fmt.Sprintf(str.Result(), convertObj...)

				fmt.Println(result.Result())

				return &object.Empty{}
			default:
				return newError("output() first variable is must be string, your definition is %s", str.Type())
			}
		},
	},

	"input": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) <= 0 {
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}

			switch text := args[0].(type) {
			case *object.String:
				fmt.Print(text.Result())

			}

			var stdin *bufio.Reader
			var line []rune
			var c rune
			var err error

			stdin = bufio.NewReader(os.Stdin)

			for {
				c, _, _ = stdin.ReadRune()
				if err == io.EOF || c == '\n' {
					break
				}
				line = append(line, c)
			}

			return &object.String{Value: string(line[:])}
		},
	},

	"encrypt": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) < 2 {
				return newError("encrypt() function needs 2 arguments, got %s", len(args))
			}

			switch key := args[0].(type) {
			case *object.String:
				switch data := args[1].(type) {
				case *object.String:
					enc := crypto.New(key.Result())
					return &object.String{Value: enc.Encrypt(data.Result())}
				default:
					return newError("encrypt() function second value (data) must be string, got %s", data.Type())
				}
			default:
				return newError("encrypt() function first value (key) must be string, got %s", key.Type())
			}
		},
	},

	"decrypt": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) < 2 {
				return newError("decrypt() function needs 2 arguments, got %s", len(args))
			}

			switch key := args[0].(type) {
			case *object.String:
				switch data := args[1].(type) {
				case *object.String:
					enc := crypto.New(key.Result())
					return &object.String{Value: enc.Decrypt(data.Result())}
				default:
					return newError("encrypt() function second value (data) must be string, got %s", data.Type())
				}
			default:
				return newError("encrypt() function first value (key) must be string, got %s", key.Type())
			}
		},
	},
	"hash_encode": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) < 2 {
				return newError("hash_encode() function needs 2 arguments, got %s", len(args))
			}

			switch hashType := args[0].(type) {
			case *object.String:
				switch hashType.Result() {
				case "base64":
					switch data := args[1].(type) {
					case *object.String:
						encodedString := base64.StdEncoding.EncodeToString([]byte(data.Result()))
						return &object.String{Value: encodedString}
					default:
						return newError("hash_encode() function second value (data) must be string, got %s", data.Type())
					}
				case "md5":
					switch data := args[1].(type) {
					case *object.String:
						hasher := md5.New()
						hasher.Write([]byte(data.Result()))
						return &object.String{Value: hex.EncodeToString(hasher.Sum(nil))}
					default:
						return newError("hash_encode() function second value (data) must be string, got %s", data.Type())
					}
				}

			default:
				return newError("hash_encode() function first value (type) must be string, got %s", hashType.Type())
			}
			return &object.Empty{}
		},
	},

	"hash_decode": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) < 2 {
				return newError("hash_decode() function needs 2 arguments, got %s", len(args))
			}

			switch hashType := args[0].(type) {
			case *object.String:
				switch hashType.Result() {
				case "base64":
					switch data := args[1].(type) {
					case *object.String:
						originalStringBytes, err := base64.StdEncoding.DecodeString(data.Result())
						if err != nil {
							newError("hash_decode() Some error occured during base64 decode. Error: %s", err.Error())
						}
						return &object.String{Value: string(originalStringBytes)}
					default:
						return newError("hash_decode() function second value (data) must be string, got %s", data.Type())
					}
				case "md5":
					switch data := args[1].(type) {
					case *object.String:
						return newError("hash_decode() function not support %s decode", hashType)
					default:
						return newError("hash_decode() function second value (data) must be string, got %s", data.Type())
					}
				}

			default:
				return newError("hash_decode() function first value (type) must be string, got %s", hashType.Type())
			}
			return &object.Empty{}
		},
	},
	"type": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) < 1 {
				return newError("type() function needs 1 arguments, got %s", len(args))
			}
			return &object.String{Value: string(args[0].Type())}
		},
	},
	"fwatch": {
		Fn: func(args ...object.Object) object.Object {
			if len(args) < 2 {
				return newError("fwatch() function needs 2 arguments, got %s", len(args))
			}

			switch file := args[0].(type) {
			case *object.String:
				switch fn := args[1].(type) {
				case *object.Function:
					w := watcher.New()

					w.Add(file.Result())

					w.Run(func() {
						obj := make([]object.Object, 0, 1)
						ApplyFunctionInside(fn, obj)
					})
				default:
					return newError("fwatch() function second must be function, got %s", fn.Type())
				}
			default:
				return newError("fwatch() function first value must be string, got %s", file.Type())
			}

			return &object.Empty{}
		},
	},
}

var PreBuiltFunctions = builtins