package object

import (
	"bytes"
	"fmt"
	"hash/fnv"
	"net/http"
	"os/exec"
	"strings"

	"github.com/gorilla/mux"

	"gitlab.com/olanguage/olang/ast"
	"gitlab.com/olanguage/olang/color"
)

type ObjectType string

const (
	INTEGER_OBJ           = "INTEGER"
	BOOLEAN_OBJ           = "BOOLEAN"
	NULL_OBJ              = "NULL"
	RETURN_VALUE_OBJ      = "RETURN_VALUE"
	ERROR_OBJ             = "ERROR"
	FUNCTION_OBJ          = "FUNCTION"
	STRING_OBJ            = "STRING"
	BUILTIN_OBJ           = "BUILTIN"
	ARRAY_OBJ             = "ARRAY"
	HASH_OBJ              = "HASH"
	EMPTY_OBJ             = "EMPTY"
	LOOP                  = "LOOP"
	LIST                  = "LIST"
	LIST_OBJ              = "LIST"
	URL_OBJ               = "URL"
	SCOPE_OBJ             = "SCOPE"
	BYTE_OBJ              = "BYTE"
	PROC_OBJ              = "PROC"
	ENV_OBJ               = "ENV"
	NOTIFICATION_OBJ      = "NOTIFICATION"
	SOCKET_OBJ            = "SOCKET"
	LITERAL_OBJ           = "LITERAL"
	DATABASE_OBJ          = "DATABASE"
	MODEL_OBJ             = "MODEL"
	BEGIN_OBJ             = "BEGIN"
	FLOAT_OBJ             = "FLOAT"
	ROUTE_OBJ             = "ROUTE"
	SIGNAL_OBJ            = "SIGNAL"
	ASM_OBJ               = "ASM"
	ASYNC_OBJ             = "ASYNC"
	SERVER_OBJ            = "SERVER"
	EVAL_OBJ              = "EVAL"
	COLOR_OBJ             = "COLOR"
	IMAGE_OBJ             = "IMAGE"
	LOAD_OBJ              = "LOAD"
	COMPILED_FUNCTION_OBJ = "COMPILED_FUNCTION"
	CLOSURE_OBJ           = "CLOSURE_OBJ"
)

type Object interface {
	Type() ObjectType
	Inspect() string
	Result() string
	Debug() string
}

type Integer struct {
	Value int64
}

type Load struct {
	File   string
	Env    *Environment
	SysEnv []Object
}

type Byte struct {
	Value byte
}

type Float struct {
	Value float64
}

type Url struct {
	ResponseCode  int64
	Content       string
	ContentType   string
	ContentLength int64
	Server        string
}

type Process struct {
	Name    string
	Pid     int64
	Output  string
	Command string
	Proc    *exec.Cmd
	Param   []Object
	Error   Object
}

type SysEnv struct {
	Name  string
	Value string
}

type Notification struct {
	Title string
	Icon  string
}

type Socket struct {
	Addr            string
	Port            string
	ConType         string
	OnReady         string
	OnResponse      string
	OnRequest       string
	Env             *Environment
	Messages        []Message
	Debugger        bool
	DefaultResponse Object
}

type Message struct {
	Request  string
	Response string
}

type Route struct {
	Config Object
}

type File struct {
	Filename Object
	MimeType Object
	Content  byte
}

type Signal struct {
	Name  string
	Value []byte
}

type Server struct {
	Server *http.Server
	Router *mux.Router
}

type Eval struct {
	Content Object
}

type SyscallResult struct {
	Request  interface{}
	Response interface{}
	Error    interface{}
}

type Color struct {
	Definition Object
	Color      interface{}
}

type Image struct {
	Name   Object
	Source interface{}
}

func (l *Load) Type() ObjectType { return LOAD_OBJ }
func (l *Load) Inspect() string {
	var out bytes.Buffer
	return out.String()
}
func (l *Load) Result() string {
	var out bytes.Buffer
	return out.String()
}

func (l *Load) Debug() string {
	var out bytes.Buffer
	return out.String()
}

func (c *Color) Type() ObjectType { return COLOR_OBJ }
func (c *Color) Inspect() string {
	var out bytes.Buffer
	return out.String()
}
func (c *Color) Result() string {
	var out bytes.Buffer
	return out.String()
}

func (c *Color) Debug() string {
	var out bytes.Buffer
	return out.String()
}

func (i *Image) Type() ObjectType { return IMAGE_OBJ }
func (i *Image) Inspect() string {
	var out bytes.Buffer
	return out.String()
}
func (i *Image) Result() string {
	var out bytes.Buffer
	return out.String()
}

func (i *Image) Debug() string {
	var out bytes.Buffer
	return out.String()
}

func (r *Server) Type() ObjectType { return SERVER_OBJ }
func (r *Server) Inspect() string {
	var out bytes.Buffer
	return out.String()
}
func (r *Server) Result() string {
	var out bytes.Buffer
	return out.String()
}

func (r *Server) Debug() string {
	var out bytes.Buffer
	return out.String()
}

func (e *Eval) Type() ObjectType { return SERVER_OBJ }
func (e *Eval) Inspect() string {
	return e.Content.Inspect()
}
func (e *Eval) Result() string {
	return e.Content.Result()
}

func (e *Eval) Debug() string {
	return e.Content.Debug()
}

func (r *Signal) Type() ObjectType { return SIGNAL_OBJ }
func (r *Signal) Inspect() string {
	var out bytes.Buffer
	return out.String()
}
func (r *Signal) Result() string {
	var out bytes.Buffer
	return out.String()
}

func (r *Signal) Debug() string {
	var out bytes.Buffer
	return out.String()
}

func (s *SyscallResult) Type() ObjectType { return SIGNAL_OBJ }
func (s *SyscallResult) Inspect() string {
	var out bytes.Buffer
	return out.String()
}
func (s *SyscallResult) Result() string {
	var out bytes.Buffer
	return out.String()
}

func (s *SyscallResult) Debug() string {
	var out bytes.Buffer
	return out.String()
}

func (r *Route) Type() ObjectType { return ROUTE_OBJ }
func (r *Route) Inspect() string {
	var out bytes.Buffer
	out.WriteString("route {")
	out.WriteString(r.Config.Inspect())
	out.WriteString("}")
	return out.String()
}
func (r *Route) Result() string {
	var out bytes.Buffer
	return out.String()
}

func (r *Route) Debug() string {
	var out bytes.Buffer
	out.WriteString("var router = " + r.Config.Debug() + ";")
	return out.String()
}

func (s *Socket) Type() ObjectType { return SOCKET_OBJ }
func (s *Socket) Inspect() string {
	var out bytes.Buffer
	out.WriteString(fmt.Sprintf("%#v\n", s))
	return out.String()
}
func (s *Socket) Result() string {
	var out bytes.Buffer
	return out.String()
}

func (s *Socket) Debug() string {
	var out bytes.Buffer
	return out.String()
}

func (s *Socket) FindResponse(request string) string {
	for _, message := range s.Messages {
		if request == message.Request {
			return message.Response
		}
	}
	return ""
}

func (s *Socket) DialServer() {}

func (s *Socket) DialMessage() {}

func (n *Notification) Type() ObjectType { return NOTIFICATION_OBJ }
func (n *Notification) Inspect() string {
	var out bytes.Buffer
	out.WriteString(fmt.Sprintf("%#v\n", n))
	return out.String()
}
func (n *Notification) Result() string {
	var out bytes.Buffer
	return out.String()
}

func (n *Notification) Debug() string {
	var out bytes.Buffer
	return out.String()
}
func (se *SysEnv) Type() ObjectType { return ENV_OBJ }
func (se *SysEnv) Inspect() string {
	var out bytes.Buffer
	out.WriteString(fmt.Sprintf("%#v\n", se))
	return out.String()
}
func (se *SysEnv) Result() string {
	var out bytes.Buffer
	out.WriteString(fmt.Sprintf("%s", se.Value))
	return out.String()
}
func (se *SysEnv) Debug() string {
	var out bytes.Buffer
	out.WriteString(fmt.Sprintf("%s", se.Value))
	return out.String()
}

func (f *Float) Type() ObjectType { return FLOAT_OBJ }
func (f *Float) Inspect() string {
	var out bytes.Buffer
	out.WriteString(fmt.Sprintf("%#v\n", f))
	return out.String()
}
func (f *Float) Result() string {
	var out bytes.Buffer
	out.WriteString(fmt.Sprintf("%g", f.Value))
	return out.String()
}
func (f *Float) Debug() string {
	var out bytes.Buffer
	out.WriteString(fmt.Sprintf("%g", f.Value))
	return out.String()
}

func (p *Process) Type() ObjectType { return PROC_OBJ }
func (p *Process) Inspect() string {
	var out bytes.Buffer
	out.WriteString(fmt.Sprintf("%#v\n", p))
	return out.String()
}
func (p *Process) Result() string {
	var out bytes.Buffer
	if p.Output != "" {
		out.WriteString(fmt.Sprintf("%s", p.Output))
	}
	return out.String()
}

func (p *Process) Debug() string {
	var out bytes.Buffer
	if p.Output != "" {
		out.WriteString(fmt.Sprintf("%s", p.Output))
	}
	return out.String()
}

func (b *Byte) Type() ObjectType { return BYTE_OBJ }
func (b *Byte) Inspect() string {
	var out bytes.Buffer
	out.WriteString(fmt.Sprintf("%#v\n", b))
	return out.String()
}
func (b *Byte) Result() string { return fmt.Sprintf("%p", b.Value) }
func (b *Byte) Debug() string  { return fmt.Sprintf("%s", string(b.Value)) }

func (u *Url) Type() ObjectType { return URL_OBJ }
func (u *Url) Inspect() string {
	var out bytes.Buffer
	out.WriteString(fmt.Sprintf("%#v\n", u))
	return out.String()
}
func (u *Url) Result() string { return fmt.Sprintf("%s", u.Content) }

func (u *Url) Debug() string { return fmt.Sprintf("%s", u.Content) }

func (i *Integer) Type() ObjectType { return INTEGER_OBJ }
func (i *Integer) Inspect() string  { return fmt.Sprintf("%d", i.Value) }
func (i *Integer) Result() string   { return fmt.Sprintf("%d", i.Value) }

func (i *Integer) Debug() string { return fmt.Sprintf("%d", i.Value) }

type Boolean struct {
	Value bool
}

func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ }
func (b *Boolean) Inspect() string {
	var out bytes.Buffer
	//out.WriteString(fmt.Sprintf("%#v\n", b))
	out.WriteString(fmt.Sprintf("%t", b.Value))
	return out.String()
}
func (b *Boolean) Result() string { return fmt.Sprintf("%t", b.Value) }
func (b *Boolean) Debug() string  { return fmt.Sprintf("%t", b.Value) }

type Null struct{}

func (n *Null) Type() ObjectType { return NULL_OBJ }
func (n *Null) Inspect() string {
	var out bytes.Buffer
	out.WriteString(fmt.Sprintf("%#v\n", n))
	return out.String()
}
func (n *Null) Result() string { return "" }
func (n *Null) Debug() string  { return "null" }

type Empty struct{}

func (n *Empty) Type() ObjectType { return EMPTY_OBJ }
func (n *Empty) Inspect() string  { return "" }
func (n *Empty) Result() string   { return "" }
func (n *Empty) Debug() string    { return "" }

type ReturnValue struct {
	Value Object
}

func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ }
func (rv *ReturnValue) Inspect() string  { return rv.Value.Inspect() }
func (rv *ReturnValue) Result() string   { return rv.Value.Result() }
func (rv *ReturnValue) Debug() string    { return rv.Value.Debug() }

type Error struct {
	Message string
}

func (e *Error) Type() ObjectType { return ERROR_OBJ }
func (e *Error) Inspect() string {
	var out bytes.Buffer
	out.WriteString(fmt.Sprintf("%#v\n", e))
	return out.String()
}
func (e *Error) Result() string {
	return color.Red("ERROR: " + e.Message)
}

func (e *Error) Debug() string { return "" }

type Warning struct {
	Message string
}

func (w *Warning) Type() ObjectType { return ERROR_OBJ }
func (w *Warning) Inspect() string {
	var out bytes.Buffer
	out.WriteString(fmt.Sprintf("%#v\n", w))
	return out.String()
}
func (w *Warning) Result() string {
	return color.Yellow("WARNING: " + w.Message)
}

func (w *Warning) Debug() string { return "" }

type Function struct {
	Parameters []*ast.Identifier
	Body       *ast.BlockStatement
	Env        *Environment
}

func (f *Function) Type() ObjectType { return FUNCTION_OBJ }
func (f *Function) Inspect() string {
	var out bytes.Buffer

	params := []string{}
	for _, p := range f.Parameters {
		params = append(params, p.String())
	}

	out.WriteString("fn")
	out.WriteString("(")
	out.WriteString(strings.Join(params, ", "))
	out.WriteString(") {\n")
	out.WriteString(f.Body.String())
	out.WriteString("\n}")

	return out.String()
}
func (f *Function) Result() string { return "" }
func (f *Function) Debug() string {
	var out bytes.Buffer

	params := []string{}
	for _, p := range f.Parameters {
		params = append(params, p.String())
	}

	out.WriteString("function")
	out.WriteString("(")
	out.WriteString(strings.Join(params, ", "))
	out.WriteString(") {\n")
	out.WriteString(f.Body.Debug())
	out.WriteString("\n}")

	return out.String()
}

type Literal struct {
	Name       string
	Parameters []*ast.Identifier
	Body       *ast.BlockStatement
	Env        *Environment
}

func (l *Literal) Type() ObjectType { return LITERAL_OBJ }
func (l *Literal) Inspect() string {
	var out bytes.Buffer

	params := []string{}
	for _, p := range l.Parameters {
		params = append(params, p.String())
	}

	out.WriteString("literal")
	out.WriteString(" " + l.Name)
	out.WriteString("(")
	out.WriteString(strings.Join(params, ", "))
	out.WriteString(") {\n")
	out.WriteString(l.Body.String())
	out.WriteString("\n}")

	return out.String()
}
func (l *Literal) Debug() string {
	var out bytes.Buffer

	params := []string{}
	for _, p := range l.Parameters {
		params = append(params, p.String())
	}

	out.WriteString("function")
	out.WriteString(" " + l.Name)
	out.WriteString("(")
	out.WriteString(strings.Join(params, ", "))
	out.WriteString(") {\n")
	out.WriteString(l.Body.String())
	out.WriteString("\n}")

	return out.String()
}

type Scope struct {
	Name string
	Body *ast.BlockStatement
	Vars []Object
	Env  *Environment
}

func (f *Scope) Type() ObjectType { return SCOPE_OBJ }
func (f *Scope) Inspect() string {
	var out bytes.Buffer

	out.WriteString("scope ")
	out.WriteString(f.Name)
	out.WriteString(" { ")
	out.WriteString(f.Body.String())
	out.WriteString(" }")

	return out.String()
}
func (f *Scope) Result() string { return "" }
func (f *Scope) Debug() string {
	var out bytes.Buffer

	out.WriteString("var ")
	out.WriteString(f.Name)
	out.WriteString(" = { ")
	out.WriteString(f.Body.Debug())
	out.WriteString(" } ")

	return out.String()
}

type String struct {
	Value string
}

func (s *String) Type() ObjectType { return STRING_OBJ }
func (s *String) Inspect() string {
	if len(s.Value) > 0 {
		return "\"" + s.Value + "\""
	}
	return ""
}
func (s *String) Result() string {
	if len(s.Value) > 0 {
		return fmt.Sprintf("%s", s.Value)
	}
	return ""
}

func (s *String) Debug() string {
	if len(s.Value) > 0 {
		return s.Value
	}
	return ""
}

type BuiltinFunction func(args ...Object) Object

type Builtin struct {
	Fn BuiltinFunction
}

func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ }
func (b *Builtin) Inspect() string  { return "builtin function" }
func (b *Builtin) Result() string   { return "" }
func (b *Builtin) Debug() string    { return "" }

type Array struct {
	Elements []Object
}

func (ao *Array) Type() ObjectType { return ARRAY_OBJ }
func (ao *Array) Inspect() string {
	var out bytes.Buffer

	elements := []string{}
	for _, e := range ao.Elements {
		elements = append(elements, e.Inspect())
	}

	out.WriteString("[")
	out.WriteString(strings.Join(elements, ", "))
	out.WriteString("]")

	return out.String()
}
func (ao *Array) Result() string { return "" }
func (ao *Array) Debug() string {
	var out bytes.Buffer

	elements := []string{}
	for _, e := range ao.Elements {
		elements = append(elements, e.Debug())
	}

	out.WriteString("[")
	out.WriteString(strings.Join(elements, ", "))
	out.WriteString("]")

	return out.String()
}

type Hashable interface {
	HashKey() HashKey
}

type HashKey struct {
	Type  ObjectType
	Value uint64
}

func (b *Boolean) HashKey() HashKey {
	var value uint64

	if b.Value {
		value = 1
	} else {
		value = 0
	}

	return HashKey{Type: b.Type(), Value: value}
}

func (i *Integer) HashKey() HashKey {
	return HashKey{Type: i.Type(), Value: uint64(i.Value)}
}

func (s *String) HashKey() HashKey {
	h := fnv.New64a()
	h.Write([]byte(s.Value))

	return HashKey{Type: s.Type(), Value: h.Sum64()}
}

type HashPair struct {
	Key   Object
	Value Object
}

type Hash struct {
	Pairs map[HashKey]HashPair
}

func (h *Hash) Type() ObjectType { return HASH_OBJ }
func (h *Hash) Inspect() string {
	var out bytes.Buffer

	pairs := []string{}
	for _, pair := range h.Pairs {
		pairs = append(pairs, fmt.Sprintf("%s: %s", pair.Key.Inspect(), pair.Value.Inspect()))
	}

	out.WriteString("{")
	out.WriteString(strings.Join(pairs, ", "))
	out.WriteString("}")

	return out.String()
}
func (h *Hash) Result() string { return "" }
func (h *Hash) Debug() string {
	var out bytes.Buffer

	pairs := []string{}
	for _, pair := range h.Pairs {
		pairs = append(pairs, fmt.Sprintf("%s: %s", pair.Key.Debug(), pair.Value.Debug()))
	}

	out.WriteString("{")
	out.WriteString(strings.Join(pairs, ", "))
	out.WriteString("}")

	return out.String()
}

type Database struct {
	DbType     string
	ConnString string
	Connection interface{}
	Error      bool
}

func (d *Database) Type() ObjectType { return DATABASE_OBJ }
func (d *Database) Inspect() string {
	var out bytes.Buffer
	out.WriteString(fmt.Sprintf("%#v\n", d))
	return out.String()
}
func (d *Database) Result() string {
	var out bytes.Buffer
	return out.String()
}

func (d *Database) Debug() string {
	var out bytes.Buffer
	return out.String()
}

type Model struct {
	Database *Database
	Name     string
	Columns  interface{}
}

func (m *Model) Type() ObjectType { return MODEL_OBJ }
func (m *Model) Inspect() string {
	var out bytes.Buffer
	out.WriteString(fmt.Sprintf("%#v\n", m))
	return out.String()
}
func (m *Model) Result() string {
	var out bytes.Buffer
	return out.String()
}

func (m *Model) Debug() string {
	var out bytes.Buffer
	return out.String()
}

type Begin struct {
	Name         string
	Error        Object
	State        bool
	ReturnResult interface{}
}

func (b *Begin) Type() ObjectType { return BEGIN_OBJ }
func (b *Begin) Inspect() string {
	var out bytes.Buffer
	out.WriteString(fmt.Sprintf("%#v\n", b))
	return out.String()
}
func (b *Begin) Result() string {
	var out bytes.Buffer
	return out.String()
}

func (b *Begin) Debug() string {
	var out bytes.Buffer
	return out.String()
}

type Async struct {
	Name         string
	ReturnResult Object
	Channel      chan Object
}

func (a *Async) Type() ObjectType { return ASYNC_OBJ }
func (a *Async) Inspect() string {
	var out bytes.Buffer
	out.WriteString(fmt.Sprintf("%#v\n", a))
	return out.String()
}
func (a *Async) Result() string {
	return a.ReturnResult.Result()
}

func (a *Async) Debug() string {
	var out bytes.Buffer
	return out.String()
}
