func main() {
	err := graphics.Init()
	if err != nil {
		log.Fatalln(err)
	}
	defer graphics.Quit()

	win, err := graphics.CreateWindow(800, 600, "Test")
	if err != nil {
		log.Fatalln(err)
	}
	defer win.Destroy()

	if len(os.Args) == 1 {
		newBackEnd()
	}

	frontend := NewPlayerFrontend(win)

	if len(os.Args) == 1 {
		joinEvent := &events.PlayerJoin{
			UUID: frontend.id,
		}
		events.SendEvent(joinEvent)

		go StartNetworkListener()
		loadlevel := events.LoadLevel{
			FileName: "assets/testTiles.json",
		}
		events.SendEvent(&loadlevel)
	} else {
		NewNetworkBackend(os.Args[1])
	}
	frontend.Mainloop()
}
func NewNetworkFrontend(conn net.Conn) *NetworkFrontend {
	n := NetworkFrontend{
		Network: Network{
			conn:    conn,
			dir:     events.DirFront,
			decoder: json.NewDecoder(conn),
			eventCh: make(chan events.Event),
			close:   make(chan struct{}),
		},
	}
	n.afterDestroy = n.sendLeave
	id, err := uuid.NewV4()
	if err != nil {
		log.Fatalln(err)
	}
	n.id = id.String()
	events.AddListener(n.eventCh, events.DirFront, 0)
	setEvent := &events.SetUUID{
		UUID: n.id,
	}
	n.sendEvent(setEvent)
	go n.readloop()
	go n.mainloop()
	joinEvent := &events.PlayerJoin{
		UUID: n.id,
	}
	events.SendEvent(events.ReloadLevel{})
	events.SendEvent(joinEvent)
	return &n
}
func (p *PlayerFrontend) Mainloop() {
	nextUpdate := time.After(10 * time.Microsecond)
loop:
	for {
		select {
		case ev := <-p.eventCh:
			p.processEvent(ev)
		case <-nextUpdate:
			nextUpdate = time.After(time.Second / 60)
			p.processInput()
			if err := p.window.Update(p.getDraw()); err != nil {
				log.Fatalln(err)
			}
		case _, ok := <-p.close:
			if !ok {
				break loop
			}
		}
	}
	events.RemoveListener(p.eventCh, events.DirFront, 0)
	for _, v := range p.units {
		v.Destroy()
	}
	leaveEvent := &events.PlayerLeave{
		UUID: p.id,
	}
	events.SendEvent(leaveEvent)
}
func (b *BackEnd) createPlayerUnit(id int, uuid string) {
	createPlayer := events.CreateUnit{
		ID:       id,
		X:        b.lastLevel.StartX,
		Y:        b.lastLevel.StartY,
		W:        32,
		H:        32,
		AttachTo: uuid,
	}
	events.SendEvent(&createPlayer)
}
func (p *PlayerFrontend) sendInput(in Input) {
	player := p.GetUnit(p.player)
	if player == nil {
		return
	}
	e := events.InputUpdate{
		ID: p.player,
		X:  in.X,
		Y:  in.Y,
	}
	events.SendEvent(&e)
}
func (n *Network) readloop() {
	for {
		var data struct {
			Type  int
			Event json.RawMessage
		}
		err := n.decoder.Decode(&data)
		if err != nil {
			if n, ok := err.(net.Error); ok && n.Temporary() {
				continue
			}
			log.Println(err)
			break
		}
		ev, err := events.DecodeJSON(data.Type, data.Event)
		if err != nil {
			log.Println(err)
			break
		}
		ev.SetDuplicate(true)
		events.SendEvent(ev)
	}
}
func (b *BackEnd) processEvent(ev events.Event) {
	switch e := ev.(type) {
	case *events.CreateUnit:
		b.unitInfo = append(b.unitInfo, NewUnit(e.X, e.Y, PlayerT, e.ID, b))
	case *events.DestroyUnit:
		for i, unit := range b.unitInfo {
			if unit.unitID == e.ID {
				b.unitInfo = append(b.unitInfo[:i], b.unitInfo[i+1:]...)
				break
			}
		}
	case *events.ChangeLevel:
		b.lastLevel = e
		for _, unit := range b.unitInfo {
			unit.Destroy()
		}
		b.unitInfo = make([]*unit, 0, len(e.Units))
		b.nextID = 1
		for _, unit := range e.Units {
			b.processEvent(&unit)
			if b.nextID <= unit.ID {
				b.nextID = unit.ID + 1
			}
		}
		for k, v := range b.players {
			if _, ok := e.Players[k]; !ok {
				if b.nextID <= v {
					b.nextID = v + 1
				}
				b.createPlayerUnit(v, k)
			}
		}
	case events.ReloadLevel:
		units := make([]events.CreateUnit, len(b.unitInfo))
		for i, unit := range b.unitInfo {
			units[i] = events.CreateUnit{
				ID: unit.unitID,
				X:  unit.x,
				Y:  unit.y,
				W:  32,
				H:  32,
			}
		}
		newLevel := &events.ChangeLevel{
			// TODO Change this to copy the contents of Tilemaps
			Tilemaps:   b.lastLevel.Tilemaps,
			Images:     b.lastLevel.Images,
			TileWidth:  b.lastLevel.TileWidth,
			TileHeight: b.lastLevel.TileHeight,
			StartX:     b.lastLevel.StartX,
			StartY:     b.lastLevel.StartY,
			CollideMap: b.lastLevel.CollideMap,
			Pits:       b.lastLevel.Pits,
			Units:      units,
			Players:    b.players,
		}
		events.SendEvent(newLevel)
	case *events.PlayerJoin:
		id := b.nextID
		b.players[e.UUID] = id
		b.nextID++
		if b.lastLevel == nil {
			return
		}
		b.createPlayerUnit(id, e.UUID)
	case *events.LoadLevel:
		b.loadLevel(e)
	case *events.PlayerLeave:
		destroy := events.DestroyUnit{
			ID: b.players[e.UUID],
		}
		events.SendEvent(&destroy)
		delete(b.players, e.UUID)
	}
}
func (b *BackEnd) loadLevel(e *events.LoadLevel) {
	type level struct {
		Height     int32
		Width      int32
		Tileheight int32
		Tilewidth  int32
		Layers     []struct {
			Data       []int
			Properties map[string]string
			Height     int32
			Width      int32
		}
		Tilesets []struct {
			Image          string
			Tilewidth      int32
			Tileheight     int32
			Tileproperties map[string]map[string]string
		}
		Properties map[string]string
	}

	file, err := os.Open(e.FileName)
	if err != nil {
		log.Printf("failed loading file: %s\n", e.FileName)
		return
	}
	defer file.Close()

	var x level
	err = json.NewDecoder(file).Decode(&x)
	if err != nil {
		log.Printf("failed loading file: %s\n", e.FileName)
		return
	}

	//changeLevel struct
	//load the change

	startX, err := strconv.Atoi(x.Properties["StartX"])
	startY, err := strconv.Atoi(x.Properties["StartY"])
	cLevel := events.ChangeLevel{
		Tilemaps:   make([]events.Tilemap, 0, len(x.Tilesets)),
		Images:     [][][]int{},
		TileWidth:  x.Tilewidth,
		TileHeight: x.Tileheight,
		StartX:     float64(startX)*float64(x.Tilewidth) + float64(x.Tilewidth)*.5,
		StartY:     float64(startY)*float64(x.Tileheight) + float64(x.Tileheight)*.5,
		CollideMap: make([][]bool, x.Height),
		Units:      make([]events.CreateUnit, 0, len(b.players)),
		Players:    map[string]int{},
		Pits:       make([][]bool, x.Height),
	}

	b.nextID = 1
	for playerID := range b.players {
		create := events.CreateUnit{
			ID:       b.nextID,
			X:        cLevel.StartX,
			Y:        cLevel.StartY,
			W:        32,
			H:        32,
			AttachTo: playerID,
		}
		b.players[playerID] = b.nextID
		cLevel.Players[playerID] = b.nextID
		cLevel.Units = append(cLevel.Units, create)
		b.nextID++
	}

	for i := range cLevel.CollideMap {
		cLevel.CollideMap[i] = make([]bool, x.Width)
		cLevel.Pits[i] = make([]bool, x.Width)
	}

	for _, t := range x.Tilesets {
		tm := events.Tilemap{
			Filename:   t.Image,
			TileWidth:  t.Tilewidth,
			TileHeight: t.Tileheight,
		}
		cLevel.Tilemaps = append(cLevel.Tilemaps, tm)
	}

	for z, layer := range x.Layers {
		cLevel.Images = append(cLevel.Images, [][]int{})
		var i int32
		for i = 0; i < layer.Height; i++ {
			cLevel.Images[z] = append(cLevel.Images[z], layer.Data[i*layer.Width:(i+1)*layer.Width])
		}
		for yC, row := range cLevel.Images[z] {
			for xC, tile := range row {
				for _, tileset := range x.Tilesets {
					for k, v := range tileset.Tileproperties {
						if v["Pit"] == "True" {
							if strconv.Itoa(tile-1) == k {
								cLevel.Pits[yC][xC] = true
							}
						}
					}
				}
				if layer.Properties["collide"] == "true" {
					if tile != 0 {
						cLevel.CollideMap[yC][xC] = true
					}
				}
			}
		}
	}
	events.SendEvent(&cLevel)
}
func (n *NetworkFrontend) sendLeave() {
	leave := events.PlayerLeave{
		UUID: n.id,
	}
	events.SendEvent(&leave)
}
//has to do with everything about the unit
func (u *unit) updateUnit() {
	curRect := u.typ.hitDetect.collisionBox(int32(u.x), int32(u.y))
	newX := u.x + u.xV
	if u.xV > 0 {
		//right
		currBox := curRect.rightBox
		currBox.right += int32(u.xV)
		tileX, _ := currBox.checkRect(u.backAccess.lastLevel.CollideMap, 1, 0, u.backAccess.lastLevel.TileWidth, u.backAccess.lastLevel.TileHeight)
		if tileX != -1 {
			newX = float64(int32(tileX)*u.backAccess.lastLevel.TileWidth - u.typ.hitDetect.rightBox.right - 1)
			u.xV = 0
		}
	} else {
		//left
		currbox := curRect.leftBox
		currbox.left += int32(u.xV)
		tileX, _ := currbox.checkRect(u.backAccess.lastLevel.CollideMap, -1, 0, u.backAccess.lastLevel.TileWidth, u.backAccess.lastLevel.TileHeight)
		if tileX != -1 {
			newX = float64(int32(tileX+1)*u.backAccess.lastLevel.TileWidth - u.typ.hitDetect.leftBox.left)
			u.xV = 0
		}
	}

	curRect = u.typ.hitDetect.collisionBox(int32(newX), int32(u.y))
	newY := u.y + u.yV
	if u.yV > 0 {
		//down
		currbox := curRect.bottomBox
		currbox.bottom += int32(u.yV)
		_, tileY := currbox.checkRect(u.backAccess.lastLevel.CollideMap, 0, 1, u.backAccess.lastLevel.TileWidth, u.backAccess.lastLevel.TileHeight)
		if tileY != -1 {
			newY = float64(int32(tileY)*u.backAccess.lastLevel.TileHeight - u.typ.hitDetect.bottomBox.bottom - 1)
			u.yV = 0
		}
	} else {
		//up
		currBox := curRect.topBox
		currBox.top += int32(u.yV)
		_, tileY := currBox.checkRect(u.backAccess.lastLevel.CollideMap, 0, -1, u.backAccess.lastLevel.TileWidth, u.backAccess.lastLevel.TileHeight)
		if tileY != -1 {
			newY = float64(int32(tileY+1)*u.backAccess.lastLevel.TileHeight - u.typ.hitDetect.topBox.top)
			u.yV = 0
		}
	}

	u.x = newX
	u.y = newY
	u.xV += u.xAcl + (-u.xV * 0.8)
	u.yV += u.yAcl + (-u.yV * 0.8)
	if u.backAccess.lastLevel.Pits[int32(u.y)/u.backAccess.lastLevel.TileHeight][int32(u.x)/u.backAccess.lastLevel.TileWidth] {
		u.x = u.backAccess.lastLevel.StartX
		u.y = u.backAccess.lastLevel.StartY
		u.xV = 0
		u.yV = 0
	}
	e := events.UnitMoved{
		ID:   u.unitID,
		NewX: u.x,
		NewY: u.y,
	}
	events.SendEvent(&e)
}