#################################################################
##  CS 3200 (Fall 2025), Assignment #1                         ##
##  Program File Name: mySearchClasses.py                      ##
##       Student Name: Todd Wareham                            ##
##         Login Name: harold                                  ##
##              MUN #: 8008765                                 ##
#################################################################

## Provided classes for Assigment #1.
##
##      *** DO NOT CHANGE THE CODE IN THIS FILE ***
##

## This program uses a factored representation as a |F|-length
#@  list of character strings corresponding to the set F of facts
#@  in the given problem. When representing states, string i in this
##  list will have value "0" ("1") if that fact is false (true). This
##  scheme will also hold in the representation of problem goals and
##  action causes and consequences with the addition that string i 
##  wil have value "*" if fact i is not part of the represented goal,
##  cause, or consequence. The correspondence between fact names and
##  indices in the factored representation is stored in attribute factD
##  of the Problem class (see below).


## Problem state class
## Attributes:
##   string[]  stateRep

class State:

   def __init__(self, sr):
       self.stateRep = sr

   def __str__(self):
      return str(self.stateRep)

   def getStateRep(self):
      return list(self.stateRep)


## Problem action class
## Attributes:
##   string[]  cause, consequence
##   int       number, cost

class Action:

   def __init__(self, cse, con, cst, num):
      self.number = num
      self.cause = cse
      self.consequence = con
      self.cost = cst

   def __str__(self):
      return str(self.number + 1) + ": " +  \
             str(self.cause) + " => " + str(self.consequence) + \
             " (" + str(self.cost) + ")"

   def getNumber(self):
      return self.number

   def getCost(self):
      return self.cost

   def getCause(self):
      return list(self.cause)

   def getConsequence(self):
      return list(self.consequence)


#$# Problem class
## Attributes:
##   Action[]   actions
##   Dictionary factD (key = fact name, 
##                     value = fact index in factored state representation);
##                    this attribute is private to this class.
##   State      initialState
##   string[]   goal

class Problem:

   def __init__(self, filename):
      f = open(filename, "r+")
      numFacts = int(f.readline().strip().split()[1])
      self.factD = {}
      for i in range(numFacts):
         self.factD[f.readline().strip()] = i
      numActions = int(f.readline().strip().split()[1])
      self.actions = []
      for i in range(numActions):
         s = f.readline().strip().split(":")
         cost = int(s[0])

         s = s[1].split("=>")
         causeStr = s[0].strip()
         causeRep = ["*" for j in range(numFacts)]
         for elem in causeStr.split("+"):
            elem = elem.strip()
            if (elem[0:1] == "-"):
               causeRep[self.factD[elem[1:]]] = "0"
            else:
               causeRep[self.factD[elem]] = "1"

         consequenceStr = s[1].strip()
         consequenceRep = ["*" for j in range(numFacts)]
         for elem in consequenceStr.split("+"):
            elem = elem.strip()
            if (elem[0:1] == "-"):
               consequenceRep[self.factD[elem[1:]]] = "0"
            else:
               consequenceRep[self.factD[elem]] = "1"

         self.actions.append(Action(causeRep, consequenceRep, cost, i))

      s = f.readline().strip()
      initialStateRep = ["0" for j in range(numFacts)]
      for elem in f.readline().split("+"):
         elem = elem.strip()
         initialStateRep[self.factD[elem]] = "1"
      self.initialState = State(initialStateRep)

      f.readline()
      goalRep = ["*" for j in range(numFacts)]
      for elem in f.readline().split("+"):
         elem = elem.strip()
         if (elem[0:1] == "-"):
            goalRep[self.factD[elem[1:]]] = "0"
         else:
            goalRep[self.factD[elem]] = "1"
      self.goal = goalRep

   def printDesc(self):
      print("Facts: ")
      i = 1
      for elem in self.factD:
         print("   " + str(i) + ": " +  elem)
         i = i + 1
      print("Actions:")
      for a in self.actions:
         print("   " + str(a))
      print("Initial State:\n    " + str(self.initialState))
      print("Goal:\n    " + str(self.goal))

   def getInitialState(self):
      return self.initialState

   def getActions(self):
      return self.actions

   def isGoal(self, s):
      sr = s.getStateRep()
      for i in  range(len(sr)):
         if self.goal[i] == "*":
            continue
         if self.goal[i] != sr[i]:
            return False
      return True

   def printSolution(self, solutionNumber, node):
      n = node
      actionList = []
      while (n.parent != None):
         actionList.append(n.getAction().getNumber())
         n = n.getParent()
      actionList.reverse()
      actionStr = ""
      for i in range(len(actionList)):
         actionStr = actionStr + "->" + str(actionList[i] + 1)
      print(str(solutionNumber) + ": " +
            str(self.initialState.getStateRep()) + actionStr +  
             "->" + str(node.getState().getStateRep()) + 
             "[" + str(node.getG()) + "]" )


## Search tree node class
## Attributes:
##   State  state
##   Node   parent
##   Action action
##   int    g, depth

class Node:

   def __init__(self, s, par, act, gval, dep):
      self.state = s
      self.parent = par
      self.action = act
      self.g = gval
      self.depth = dep

   def __str__(self):
      return "(" + str(self.state) + "|" +  str(self.parent) + ":" + \
             str(self.action) + " [" + str(self.g) + "|" + \
             str(self.depth) + "])" 

   def getState(self):
      return self.state

   def getParent(self):
      return self.parent

   def getAction(self):
      return self.action

   def getG(self):
      return self.g

   def getDepth(self):
      return self.depth


## Node stack class
## Attributes:
##   Node[]   stack

class NodeStack:

   def __init__(self, node):
      self.stack = [node]

   def __str__(self):
      s = ""
      for n in self.stack:
         s = s + str(n) + "\n"
      return s

   def isEmpty(self):
      return True if (len(self.stack) == 0) else False

   def add(self, nodes):
      for n in nodes:
         self.stack.insert(0, n)
         
   def pop(self):
      result = self.stack[0]
      self.stack = self.stack[1:]
      return result


## Node queue class
## Attributes:
##   Node[]   queue

class NodeQueue:

   def __init__(self, node):
      self.queue = [node]

   def __str__(self):
      s = ""
      for n in self.queue:
         s = s + str(n) + "\n"
      return s

   def size(self):
      return len(self.queue)

   def isEmpty(self):
      return True if (len(self.queue) == 0) else False

   def add(self, nodes):
      for n in nodes:
         self.queue.append(n)
         
   def pop(self):
      result = self.queue[0]
      self.queue = self.queue[1:]
      return result


## Node priority queue class
## Attributes:
##   [][]   priorityQue (each element of the main list is a two-item
##                       list consisting of a node's g-value and a
##                       pointer to that node's Node object)

class NodePriorityQueue:

   def __init__(self, node):
      self.priorityQueue = [[node.getG(), node],]

   def __str__(self):
      s = ""
      for n in self.priorityQueue:
         s = s + str(n) + "\n"
      return s

   def isEmpty(self):
      return True if (len(self.priorityQueue) == 0) else False

   def add(self, nodes):
      for n in nodes:
         self.priorityQueue.append([n.getG(), n])
         
   def popMin(self):
      self.priorityQueue.sort(key=lambda x: x[0])
      result = (self.priorityQueue[0])[1]
      self.priorityQueue = self.priorityQueue[1:]
      return result

