# -*- coding: utf-8 -*-

from __future__ import print_function
# ------------------------------------------------------------------------------------------------
# Copyright (c) 2016 Microsoft Corporation
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
# associated documentation files (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge, publish, distribute,
# sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in all copies or
# substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# ------------------------------------------------------------------------------------------------

# Sample to demonstrate use of RewardForCollectingItem mission handler - creates a map with randomly distributed food items, each of which
# gives the agent a certain reward. Agent runs around collecting items, and turns left or right depending on the detected reward.
# Also demonstrates use of ObservationFromNearbyEntities

from builtins import range
import MalmoPython
import os
import random
import sys
import time
import json
import random
import errno
from collections import namedtuple
import copy
from methods import *

EntityInfo = namedtuple('EntityInfo', 'x, y, z, yaw, pitch, name, colour, variation, quantity, life, distance')
EntityInfo.__new__.__defaults__ = (0, 0, 0, 0, 0, "", "", "", 1, "")

def GetMissionXML(summary, itemDrawingXML):
    ''' Build an XML mission string that uses the RewardForCollectingItem mission handler.'''
    
    return '''<?xml version="1.0" encoding="UTF-8" ?>
    <Mission xmlns="http://ProjectMalmo.microsoft.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <About>
            <Summary>''' + summary + '''</Summary>
        </About>

        <ServerSection>
            <ServerHandlers>
                <FlatWorldGenerator generatorString="3;7,220*1,5*3,2;3;,biome_1" />
                <DrawingDecorator>
                    <DrawCuboid x1="-50" y1="226" z1="-50" x2="50" y2="226" z2="50" type="carpet" colour="RED" face="UP"/>
                    ''' + itemDrawingXML + '''
                </DrawingDecorator>
                <ServerQuitFromTimeUp timeLimitMs="30000"/>
                <ServerQuitWhenAnyAgentFinishes />
            </ServerHandlers>
        </ServerSection>

        <AgentSection mode="Survival">
            <Name>OSS117</Name>
            <AgentStart>
                <Placement x="0.5" y="227.0" z="0.5"/>
                <Inventory>
                </Inventory>
            </AgentStart>
            <AgentHandlers>
                <AbsoluteMovementCommands/>
                <DiscreteMovementCommands />
                <VideoProducer>
                    <Width>480</Width>
                    <Height>320</Height>
                </VideoProducer>
                <RewardForCollectingItem>
                    <Item reward="2" type="apple cake"/>
                    <!--Item reward="1" type="water_bucket mushroom_stew"/>
                    <Item reward="-1" type="apple cake"/>
                    <Item reward="-2" type="sugar cake cookie pumpkin_pie"/-->
                </RewardForCollectingItem>
                <ContinuousMovementCommands turnSpeedDegs="240"/>
                <ChatCommands/>
                <ObservationFromFullStats/>
                <ObservationFromNearbyEntities>
                    <Range name="close_entities" xrange="2" yrange="2" zrange="2" />
                    <Range name="far_entities" xrange="10" yrange="2" zrange="10" update_frequency="10" />
                </ObservationFromNearbyEntities>
            </AgentHandlers>
        </AgentSection>

    </Mission>'''
  
  
def GetItemDrawingXML():
    ''' Build an XML string that contains 400 randomly positioned bits of food'''
    xml=""
    for item in range(100):
        x = str(random.randint(-50,50))
        z = str(random.randint(-50,50))
        type = random.choice(["apple", "cake"])#, "water_bucket", "mushroom_stew"])
        xml += '''<DrawItem x="''' + x + '''" y="250" z="''' + z + '''" type="''' + type + '''"/>'''
    return xml

def SetVelocity(vel): 
    agent_host.sendCommand( "move " + str(vel) )

def SetTurn(turn):
    agent_host.sendCommand( "turn " + str(turn) )

def SetYaw(yaw):
    agent_host.sendCommand( "setYaw " + str(yaw) )

recordingsDirectory="EatingRecordings"
try:
    os.makedirs(recordingsDirectory)
except OSError as exception:
    if exception.errno != errno.EEXIST: # ignore error if already existed
        raise

if sys.version_info[0] == 2:
    sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)  # flush print output immediately
else:
    import functools
    print = functools.partial(print, flush=True)

validate = True
# Create a pool of Minecraft Mod clients.
# By default, mods will choose consecutive mission control ports, starting at 10000,
# so running four mods locally should produce the following pool by default (assuming nothing else
# is using these ports):
my_client_pool = MalmoPython.ClientPool()
my_client_pool.add(MalmoPython.ClientInfo("127.0.0.1", 10000))
my_client_pool.add(MalmoPython.ClientInfo("127.0.0.1", 10001))
my_client_pool.add(MalmoPython.ClientInfo("127.0.0.1", 10002))
my_client_pool.add(MalmoPython.ClientInfo("127.0.0.1", 10003))

agent_host = MalmoPython.AgentHost()
try:
    agent_host.parse( sys.argv )
except RuntimeError as e:
    print('ERROR:',e)
    print(agent_host.getUsage())
    exit(1)
if agent_host.receivedArgument("help"):
    print(agent_host.getUsage())
    exit(0)

itemdrawingxml = GetItemDrawingXML()

if agent_host.receivedArgument("test"):
    num_reps = 1
else:
    num_reps = 30000

TurnCount = 0

for iRepeat in range(1):
    my_mission = MalmoPython.MissionSpec(GetMissionXML("Nom nom nom run #" + str(iRepeat), itemdrawingxml),validate)
    # Set up a recording
    my_mission_record = MalmoPython.MissionRecordSpec(recordingsDirectory + "//" + "Mission_" + str(iRepeat) + ".tgz")
    my_mission_record.recordRewards()
    my_mission_record.recordMP4(24,400000)
    max_retries = 3
    for retry in range(max_retries):
        try:
            # Attempt to start the mission:
            agent_host.startMission( my_mission, my_client_pool, my_mission_record, 0, "itemTestExperiment" )
            break
        except RuntimeError as e:
            if retry == max_retries - 1:
                print("Error starting mission",e)
                print("Is the game running?")
                exit(1)
            else:
                time.sleep(2)

    world_state = agent_host.getWorldState()
    while not world_state.has_mission_begun:
        time.sleep(0.1)
        world_state = agent_host.getWorldState()

    reward = 0.0    # keep track of reward for this mission.
    turncount = 0   # for counting turn time.


    SetTurn(0.2)


    currTurnCount=0
    currTrial = 0
    turnTrials = 1
    turnCounts = np.zeros(turnTrials)
    turnCaliberated = False

    with open('../moment.json') as fp:
        base_moment = json.load(fp)


    def decide(items) :
        item = items[0]
        return str(item["x"]) + " " + str(item["y"]) + " " + str(item["z"])

    def caliberateTurn(prevYaw, yaw) :
        global currTrial, currTurnCount, turnCounts, turnTrials, turnCaliberated, TurnCount
        if yaw >= prevYaw :
            currTurnCount += 1
        else :
            print('completed a turn :', currTurnCount, currTrial)
            turnCounts[currTrial] = currTurnCount
            currTurnCount = 0
            currTrial += 1

        if currTrial < turnTrials : return False
        else :
            TurnCount = np.mean(turnCounts)
            print('Done caliberating. Average turnCount per 360⁰ : ', TurnCount)
            turnCaliberated = True
            SetTurn(0)
            # start running:
            #SetVelocity(.5)
            return True

    prevYaw = 0
    waitMoveCount = 0
    nextP = ""
    deciding = False
    readyToMove = True
    turning = False
    # main loop:
    while world_state.is_mission_running:
        reachitems, seeitems, appearitems = [], [], []
        world_state = agent_host.getWorldState()
        if world_state.number_of_observations_since_last_state > 0:
            msg = world_state.observations[-1].text
            ob = json.loads(msg)
            moment = copy.copy(base_moment)
            prepareMoment(moment, ob)
            currYaw = moment['state']['location']['orientation']["yaw"]
            if not turnCaliberated and not caliberateTurn(prevYaw, currYaw) :
                prevYaw = currYaw
                continue


            if not readyToMove : continue
            else :
                SetVelocity(.5)

            seenames, appearnames = "", ""
            for re in moment['observation']['reach']:
                reachitems.append(re)
            for se in moment['observation']['see']:
                seeitems.append(se)
                seenames += se["name"] + " "
                print (se["name"], se["x"], se["z"], se["yaw"], getYawDelta(se["x"], se["z"], moment['state']['location']['position']["x"], moment['state']['location']['position']["z"], currYaw))
            for ap in moment['observation']['appear']:
                appearitems.append(ap)
                appearnames += ap["name"] + " "
                print (ap["name"], ap["x"], ap["z"], ap["yaw"], getYawDelta(ap["x"], ap["z"], moment['state']['location']['position']["x"], moment['state']['location']['position']["z"], currYaw))

            if not deciding and len(appearitems) > 0 :
                print ("found in appear")
                print(appearnames)
                agent_host.sendCommand("chat " + "Appeared " + str(appearnames))

            if not deciding and len(seeitems) > 0 :
                print ("found in see")
                print(seenames)
                agent_host.sendCommand("chat " + "Seeing " + str(seenames))
                SetVelocity(0)
                deciding = True
                readyToMove = False
                nextP = decide(seeitems)
                item = seeitems[0]
                toturn = getYawDelta(item["x"], item["z"], moment['state']['location']['position']["x"], moment['state']['location']['position']["z"], currYaw)
                turncount = (toturn * np.mean(turnCounts) // 360.0)
                print ("To turn : ", turncount)



        if world_state.number_of_rewards_since_last_state > 0:
            # A reward signal has come in - see what it is:
            delta = world_state.rewards[0].getValue() 

        if turnCaliberated and not turning:
            if turncount < 0:  # Turn left
                turncount = 1 - turncount
                SetTurn(-.2)  # Start turning
            else:  # Turn right
                turncount = 1 + turncount
                SetTurn(.2)  # Start turning
            turning = True

        if turncount > 0:
            turncount -= 1  # Decrement the turn count
            if turncount == 0:
                SetTurn(0)  # Stop turning
                turning = False
                readyToMove = True
        time.sleep(0.1)
        
    # mission has ended.
    print("Mission " + str(iRepeat+1) + ": Reward = " + str(reward))
    for error in world_state.errors:
        print("Error:",error.text)
    time.sleep(0.5) # Give the mod a little time to prepare for the next mission.
