# AUTHOR: Kale Miller
# DESCRIPTION: The 'main brain' of the program is held in here.

# 50726f6772616d6d696e6720697320627265616b696e67206f66206f6e652062696720696d706f737369626c65207461736b20696e746f20736576
# 6572616c207665727920736d616c6c20706f737369626c65207461736b732e

# DEVELOPMENT LOG:
#    07/12/16: Initialized file. Moved IDGenerator class into the script. Added holding bay class.
#    12/12/16: Tweaked the IDGenerator class to help remove dependancy.
#    13/12/16: Fleshed out the NewHoldingBay class.
#    15/12/16: Added methods to add auxilary labels. Added method to generate information label. Small bug fixes.


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~IMPORTS/GLOBALS~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
import os, time
import numpy as np
from lib import containers

CONTAINER_CLASSES = [
    containers.BasicContainer,
    containers.HeavyContainer,
    containers.RefrigeratedContainer,
    containers.LiquidContainer,
    containers.ExplosivesContainer,
    containers.ToxicContainer,
    containers.ChemicalContainer
]
CONTAINER_TYPES = ['basic', 'heavy', 'refrigerated', 'liquid', 'explosive', 'toxic', 'chemical']
SERIAL_CODES = ['B', 'H', 'R', 'L', 'E', 'T', 'C']
TAG_APPLICATION_TIME = 0.2
PRINTALL_TIME = 1
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~.:.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~MAIN~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def processshipfile(filename, path):
    """Processes the csv file that the ship supplies."""

    def _deletenewline(string):
        """Deletes the \n symbol from a string if it exists."""
        try:
            truncatedstring = string[:string.index('\n')]
        except ValueError:
            truncatedstring = string
        finally:
            return truncatedstring

    try:
        home = os.getcwd()
        os.chdir(path)
    except WindowsError:  # Would this hold true on all machines?
        raise NameError, "The path specified does not exist."
    rawfile = open(filename, 'r')
    arylines = rawfile.readlines()
    basematrix = map(lambda x: _deletenewline(x).split(','), arylines)
    numpyarray = np.array(basematrix)
    return numpyarray


class IDGenerator:
    """Controls the assignment of id tags on the containers."""
    # TODO: Change the __init__ such that it works by reading a collection of tuples instead of two lists.

    def __init__(self):
        """Initialise the id generator."""
        self._COUNTERS = [0] * len(CONTAINER_TYPES)
        return

    def _findindex(self, container):
        """Determines the index in the lists the class should use."""
        return CONTAINER_TYPES.index(container)

    def _serialcode(self, index):
        """Fetches the serial code for a supplied index."""
        return SERIAL_CODES[index]

    def _counter(self, index):
        """Fetches the counter for a specific serial type and increments it by one."""
        self._COUNTERS[index] += 1
        return self._COUNTERS[index]

    def newid(self, containertype):
        """Generates a new id."""
        ii = self._findindex(containertype)
        idtag = self._serialcode(ii) + str(self._counter(ii)).zfill(5)
        return idtag


class NewHoldingBay:
    """Creates a new holding bay for the containers. Thus it contains all of the information about the containers
    along with the methods controlling unloading and loading them."""

    def __init__(self):
        self._path = os.getcwd()
        self.idgenerator = IDGenerator()
        self.containerlist = list()

        self._iOnship = 0
        self._iLoaded = 0
        self._iHolding = 0
        return None

    def _createcontainer(self, containerstr, parameters):
        """Creates a new container class based off the first column of the CSV."""
        # TODO: Fix this method up to catch more and print useful error messages.
        if not isinstance(containerstr, str):
            raise TypeError, "The parameter passed must be a string."
        elif len(containerstr) == 1:
            try:
                ii = SERIAL_CODES.index(containerstr)
            except ValueError:
                raise Exception("Bad input.")  # TODO: Fix this area up.
        elif len(containerstr) != 1:
            try:
                ii = CONTAINER_TYPES.index(containerstr)
            except ValueError:
                raise Exception("Bad input.")

        idtag = self.idgenerator.newid(CONTAINER_TYPES[ii])
        return CONTAINER_CLASSES[ii](idtag, *parameters)

    def defineship(self, file):
        """Pass in the CSV file of the ship in order to unload it."""
        shipdata = processshipfile(file, self._path)
        shipdata = shipdata[1::]  # Throw out the headers.
        for line in shipdata:
            newcontainer = self._createcontainer(line[0], (line[1], line[3]))
            self.containerlist.append(newcontainer)
            self._iOnship += 1

    def printcontainer(self, serial):
        """Prints the information about a specific container."""
        for container in self.containerlist:
            if container.id() == serial:
                container.information()
                return None
            else:
                continue
        raise NameError, "Unable to find container with serial code %s" % serial
        return -1


    def printallinformation(self):
        """Prints the information of all the containers."""
        for container in self.containerlist:
            container.information()
            time.sleep(PRINTALL_TIME)
        return None

    def unloadall(self, debug=False):
        """Unloads all of the containers from the ship."""
        for container in self.containerlist:
            container.unload(debug=debug)
            self._iHolding += 1
            self._iOnship -= 1
        return None

    def loadall(self, debug=False):
        """Loads all of the containers into trucks and trains."""
        # TODO: Proper loading locations.
        ii = 1
        for container in self.containerlist:
            container.load('Truck ' + str(ii).zfill(3), debug=debug)
            self._iHolding -= 1
            self._iLoaded += 1
            ii += 1
        return None

    def printauditedload(self):
        """Prints information about the holding bay at this time."""
        iOnship = 0; iLoaded = 0; iHolding = 0
        iContainercount = [0] * len(CONTAINER_TYPES)
        for container in self.containerlist:
            try:
                ii = CONTAINER_TYPES.index(container._type)
                iContainercount[ii] += 1
            except ValueError:
                raise NameError, "One (or more) containers don't have a valid type."

        # Print the appropriate information.
        print "----------------------------------------------------------------------"
        print "TOTAL CONTAINERS: %i" % len(self.containerlist); time.sleep(0.3)
        print "CONTAINERS CURRENTLY STILL ON SHIP: %i" % self._iOnship; time.sleep(0.3)
        print "CONTAINERS LOADED ON TRUCKS AND TRAINS: %i" % self._iLoaded; time.sleep(0.3)
        print "CONTAINERS BEING HELD IN THE HOLDING BAY: %i" % self._iHolding; time.sleep(0.3)
        print ""
        print "THE NUMBER OF CONTAINERS FOR EACH TYPE:"; time.sleep(0.3)
        for ii in xrange(len(CONTAINER_TYPES)):
            if iContainercount[ii] == 0: continue
            print "\t%s: %i" % (CONTAINER_TYPES[ii], iContainercount[ii]); time.sleep(0.3)
        print "----------------------------------------------------------------------"
        return None

    def addidtags(self, debug=False):
        """Applys appropriate serial numbers to all of the containers."""
        for container in self.containerlist:
            print "Applying id tag to container %s" % container.id()
            if not debug: time.sleep(TAG_APPLICATION_TIME)
            container.addidtag()
        return None

    def applyauxilarylabels(self):
        """Applys the labels that should go on containers about their contents and handling."""
        for container in self.containerlist:
            print "Adding labels to container %s" % container.id()
            container.addauxilarylabels()
        return None
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~.:.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

