#!/usr/bin/env python
#this is a she-bang line. you need this at the top of a script so the computer knows where the interpereter is for this program

""" A command line utility that takes an argument or the form: "{0}d{1}".format(int,int) and returns a random integer
 basically, acts as if you had rolled that dice combination, but you are not restricted to platonic dice.
 If you are importing this module, you probably want to do something like
 roll.rollmultiple(['d20','2d6'])
"""
from random import randint
"""the core random number generation function"""
# these are import statements that allow you to use code from the Python Standard Library or from python modules others have written
import  optparse
"""a module for parsing commang line options"""
import os
import sys
import unittest

import pdb
#As you might have guessed, comments start with a # in python
#there is no equivalent of /*  comment */, though there are triple-quoted-strings
#which have interesting properties.
#If they are put after a function declaration of class declaration, they
#are documentation of that function or class and are called docstrings
#you should write docstrings both so that people can read your code and so that 
#you yourself can go back and understand it later.
#you should also name your variables things that make it obvious what they do.

class DiceError(Exception):
    """
    this is a custom-defined error. it just needs to have an __init__ method and a __str__
    just prints an error when the user enters a die
    string that does not make sense
    """ 
    def __init__(self, die, problem):
        self.die = die
        self.value = problem
    def __str__(self):
        return ("{0} is not a valid format for a die.\n{1}".format(self.die,self.value))


def roll(numdice,numsides):
    """
    arg1 (int) : the number of dice to roll
    arg2 (int) : the number of sides each die has

    return (int) a random number as if arg1 arg2-sided dice had been rolled
    """
    return sum([randint(1,numsides) for i in range(numdice)])

def parsedice(diestring):
    """
    arg1 (str) : a string in the form "<int>d<int>" where the first is
    the number of dice to roll and the second the number of sides each
    die has. 

    return (tuple) a tuple of (number-of-dice,number-of-sides).
    """
    if diestring.find('d') == 0:
        diestring="1{0}".format(diestring)
    elif "d" not in diestring:
        raise DiceError(diestring,"You forgot to put a 'd'")
    try:
        return tuple([int(i) for i in diestring.split('d')])
    except ValueError:
        raise DiceError(diestring,"You have a non-integer before or after the 'd'")

def rollmultiple(dielist):
    """
    arg1 (list) : a list of strings in the form "<int>d<int>"
    representing a combination of dice.

    return (int) a possible result of having rolled the combination of dice.
    """
    sum=0
    for die in dielist:
        numdice, numsides = parsedice(die)
        sum += roll(numdice,numsides)
    return sum

def dice_session(input_file,output_file):
    """
    starts an interactive sesssion to be used for a game of D&D
    """
    prompt = input_file == sys.stdin
    if prompt:
        sys.stderr.write("please enter a space-delimited list of dice in the form \
                2d6, 4d57, or (for a single die) d20\n")
    while True :
        if prompt:
            sys.stderr.write("dice>")
        inp=input_file.readline()
        if inp in ['','q','quit','exit','\n']:
            break
        else :
            output_file.write(str(rollmultiple(inp.split(" "))) + "\n")
class Mockfile(object):
    def write(self,string):
        assert type(string) in (str,unicode)
        print string

class TestReturn(unittest.TestCase):
    def setUp(self):
        pass

    def test_cannot_write_int(self):
        mock = Mockfile()
        self.assertRaises(TypeError,lambda :mock.write(4))

    def testhasnewline(self):
        parent = self
        mock = Mockfile()
        mock.write(str(rollmultiple(["3d4","5d6"])))



    def test_parse(self):
        self.assertTrue(parsedice("3d5") == (3,5))

    def tearDown(self):
        pass

#unittest.run(ReturnTest)

def main():
    """takes a list of arguments and trats them as dice."""
    usage="roll 4d6 returns a random integer as if you had rolled four six-sided dice."
    p = optparse.OptionParser()
    p.add_option('--interactive','-i',action='store_true',dest='interactive')
    #this means, that if you type roll.py -i at the shell, then it will turn on the interactive option (see below)
    p.add_option('--file','-f',dest="input_file",default=None)
    p.add_option('--output','-o',dest="output_file",default=None)
    options, args = p.parse_args()
    if options.input_file:
        if os.path.exists(options.input_file):
            input_file = open(options.input_file,'r')

        elif os.path.exists(os.path.abspath(options.input_file)):
            input_file = open(os.path.abspath(options.input_file),'r')
        else:
            sys.stderr.write("input file %s not found",options.input_file)
    else:
        input_file = sys.stdin
        if len(args) == 0 and not options.interactive :
            p.error("You need to specify a number of dice")
    if options.output_file:
        output_file = open(os.path.abspath(options.output_file),'a')
    else:
        output_file = sys.stdout
    if options.interactive :
        dice_session(input_file,output_file)
    else:
        output_file.write(str(rollmultiple(args)) + "\n")
    # this starts a session that continually accepts dice strings
        
if __name__ == '__main__':
    unittest.main()
    main()
