# CPSC 322 Assignment 5
# copyright D. Poole 2020. Do not distribute.

def maxh(*heuristics):
    """Returns a new heuristic function that is the maximum of the functions in heuristitcs.
    the arguments, heuristics, is a tuple of heuristic functions.
    """
    # return lambda state,goal: max(h(state,goal) for h in heuristics)
    def newh(state,goal):
        return max(h(state,goal) for h in heuristics)
    return newh


def h1(state,goal):
    """ the distance to the goal location, if there is one"""
    if 'RLoc' in goal:
        return dist(state['RLoc'], goal['RLoc'])
    else:
        return 0

def h2(state,goal):
    """ the distance to the coffee shop plus getting coffee and delivering it
    if the goal has SWC=False, node has SWC=True and RHC=False
    """
    if ('SWC' in goal and goal['SWC']==False
            and state['SWC']==True
            and state['RHC']==False):
        return dist(state['RLoc'],'cs')+3 # get to coffee shop, pick-up coffee, go to office, deliver coffee
    else:
        return 0

def h3(state,goal):
    """ the distance to the mail room plus getting mail and delivering it
    if the goal has MW=False and RHM=False and state has MW=True
    """
    if ('MW' in goal and goal['MW']==False
            and 'RHM' in goal and goal['RHM']==False
            and state['MW']==True):
        return dist(state['RLoc'],'mr')+4  #get to mail room, pick-up mail, go to office, deliver mail
    else:
        return 0

def h4(state,goal):
    """the cost of not being able to carry both arises when Rob needs to get coffee and deliver mail"""
    if ('SWC' in goal and goal['SWC']==False
            and 'MW' in goal and goal['MW']==False
            and state['SWC']==True
            and state['RHC']==False
            and state['MW']==True):
        return min(dist(state['RLoc'],'mr'),dist(state['RLoc'],'cs'))+8
    else:
        return 0

def h5(state,goal):
    """the cost of not being able to carry both arises when Rob needs to get coffee and deliver mail.
    Takes into account the cost of picking up the box"""
    if ('SWC' in goal and goal['SWC']==False
            and 'MW' in goal and goal['MW']==False
            and 'RHM' in goal and goal['RHM']==False
            and state['SWC']==True
            and state['RHC']==False
            and state['MW']==True):
        boxcost = 0 if 'CB' in state and state['CB'] else 1  # needto to pick up box
        return min(dist(state['RLoc'],'mr')+6+boxcost,
                    dist(state['RLoc'],'cs')+7+boxcost)
    else:
        return 0


def dist(loc1, loc2):
    """returns the distance from location loc1 to loc2
    """
    if loc1==loc2:
        return 0
    if {loc1,loc2} in [{'cs','lab'},{'mr','off'}]:
        return 2
    else:
        return 1

#####  Forward Planner #####
from searchMPP import SearcherMPP
from stripsForwardPlanner import Forward_STRIPS
from stripsProblemCarry1 import prc1, prcb

def test(thisproblem = prc1):
    print("\n***** FORWARD NO HEURISTIC")
    print(SearcherMPP(Forward_STRIPS(thisproblem)).search())
    print("\n***** FORWARD WITH HEURISTIC 1")
    print(SearcherMPP(Forward_STRIPS(thisproblem,h1)).search()) 
    print("\n***** FORWARD WITH HEURISTIC 2")
    print(SearcherMPP(Forward_STRIPS(thisproblem,h2)).search()) 
    print("\n***** FORWARD WITH HEURISTIC 1,2")
    print(SearcherMPP(Forward_STRIPS(thisproblem,maxh(h1,h2))).search()) 
    print("\n***** FORWARD WITH HEURISTIC 1,2,3")
    print(SearcherMPP(Forward_STRIPS(thisproblem,maxh(h1,h2,h3))).search()) 
    print("\n***** FORWARD WITH HEURISTIC 1,2,3,4")
    print(SearcherMPP(Forward_STRIPS(thisproblem,maxh(h1,h2,h3,h4))).search())
    print("\n***** FORWARD WITH HEURISTIC 1,2,3,5")
    print(SearcherMPP(Forward_STRIPS(thisproblem,maxh(h1,h2,h3,h5))).search()) 


if __name__ == "__main__":
    test()

# for the domain with box:
# test(prcb)


# h4 is admissible for carrying one thing, but is *not* admissible for the original problem or for the case with the box.
# What happens if we use h4 for when carrying the box?
# h5 is admissible for carrying the box, and also for the case where the robot can only carry one thing, but is not admissible for the original problem
