#!/usr/bin/env python

# This is a short object orientated information retrieval system - it will
# collect information about a computer magazine collection and index
# the articles inside. 
# (c) 2004 Adam Cripps


#    This file is part of Newmag.
#
#       Newmag is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   Newmag is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with Newmag; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

# Please see the end of this file for brief developer documentation

import pickle # Will be using cpickle to save all the object
import sys

class MagazineCollection:
    """This class will instantiate a new collection - this is meant to represent a topic or a subject. 
    For my own personal use, I subscribe to 2 linux magazines and so they will fall in to this collection.
    The name of this object will be determined by the user from the method newcollection."""
    def search(self):
        """This will cycle through all instances of articles to find a keyword"""
        my_find = -1 # Assume  that nothing has been found
        searchfor = raw_input("What would you like to search for?")
        for collection_iter in self.titles:
            title_object = collection_iter
            for title_iter in title_object.issuesheld:
                issue_object = title_iter
                for issue_iter in issue_object.articlesheld:
                    article_object = issue_iter
                    #print article_object.name
                    name_find = article_object.name.find(searchfor)
                    author_find = article_object.author.find(searchfor)
                    keywords_find = article_object.keywords.find(searchfor)
                    if my_find <> -1 or author_find <> -1 or keywords_find <> -1:
                        print "Search result", article_object.name #, "my_find = ", my_find
                        print
                        print "Magazine Title:  ", title_object.name
                        print "Issue:           ", issue_object.name
                        print "Front cover:     ", issue_object.cover
                        print
                        print

    def export_to_html(self):
        """This method will export all the data variables to an html file """
        html = raw_input("Filename (.html)?")
        try:
            htmlfile = open(html, 'w')
        except IOError:
            print "that file doesn't exist"
            self.export_to_html(self)
        #htmlfile.write('''Content-type: text/html\n\n''')
        htmlfile.write("""<html>\n<head>\n  <title>""")
        htmlfile.write(self.name)
        htmlfile.write('''</title> \n</head>\n<body>''')
        htmlfile.write("\n<h1>")
        htmlfile.write(self.name)
        htmlfile.write("</h1>")
        # Loop through all instance objects
        counter = 1
        for collection_iter in self.titles:
        # print the headers 
            htmlfile.write("""\n<h2>""")
            htmlfile.write (collection_iter.name)
            htmlfile.write("""</h2>""")
            title_object = collection_iter
            for title_iter in title_object.issuesheld:
                # Need some code in here to put all this info in html#
                htmlfile.write("""\n <p><b>""")
                htmlfile.write (title_iter.name)
                htmlfile.write ("<br />Cover: ")
                htmlfile.write (title_iter.cover)
                htmlfile.write("""</b></p>""")
                issue_object = title_iter
                for issue_iter in issue_object.articlesheld:
                    # Need some code to add html info
                    htmlfile.write("""\n<p>""")
                    htmlfile.write("""<b>(""")
                    htmlfile.write(str(counter))
                    htmlfile.write(""")</b>""")
                    htmlfile.write("""\nTitle: """)
                    htmlfile.write(issue_iter.name)
                    htmlfile.write("""\n<br />Author: """)
                    htmlfile.write(issue_iter.author)
                    htmlfile.write("""\n<br />Keywords: """)
                    htmlfile.write(issue_iter.keywords)
                    htmlfile.write("""\n</p>""")
                    counter = counter + 1
        htmlfile.write("\n</body>")
        htmlfile.write("\n</html>")
        htmlfile.close()

    def newtitle(self):
        """This method is called from the Magazine Collection menu. The user specified that they want a new title and this is their chance to name one. """
        newtitlename = raw_input("What is the title of the magazine?")
        atitle = Title(newtitlename)
        print "This New magazine title is ", atitle.name
        #atitle.savetitle() # Will save the atitle
        self.titles.append(atitle) # titles is a list attached to this object
        # which holds all the other subclasses from Title
        #print self.title 

    def __init__(self, name):
        self.name = name
        self.titles = [ ]
        #print self.name # Just a debug to check the name is being passed correctly.
        #print "it did get here"

    def addnewissue(self):
        """This method will present the user with all the title objects for this collection object and ask which one they want to work with This is now defunct. """
        itnum = 1
        print '''Which magazine do you wish to add a new title to?'''
        for i in self.titles:
            print "Choice", itnum, "Title:", i.name
            itnum = itnum + 1
        choice = raw_input("Please enter your choice")
        choice = int(choice) # Cast it to integer
        itnum = 1
        for i in self.titles:
            #print "itnum is: ", itnum, "choice is: ", choice, "i is :", i.name
            #print "itnum is type:", type(itnum), "choice type is: ", type(choice)

            if choice == itnum:
                #Shoot off to add something to that object
                i.addissue() # go off to Title and add an issue
            else:
                itnum = itnum +1
                print "your choice was not recognised - please try again"
            self.addnewissue()
        self.issuesmenu()

    def choosetitle(self):
        """This method allows the user to choose a title that they want to work with
        """
        iter = 0
        for i in self.titles:
            print iter, ":", i.name
            iter = iter + 1
        choice = raw_input("What is your choice?")
        intchoice = int(choice)
        # intchoice = intchoice - 1 # All arrays start at 0
        if intchoice > iter:
            print "You've chosen an incorrect choice - please try again"
            self.choosetitle()
        else:
            iter_count = 0
            for iter in self.titles:
                if intchoice == iter_count:
                    iter.titlemenu()
                iter_count = iter_count +1

    def add_to_existing_issue(self):
        '''This will allow user to chose an issue and enter a new issue
        into that article. We'll loop through the objects, presenting 
        each one as an option within a menu and then collect some
        input from the user which will identify that object, right
        down to the issue level'''
        count = 0
        # Loop through the titles in this collection
        for titles_iterator in self.titles:
            print "(", count,") ", titles_iterator.name
            count = count + 1
        response = raw_input("First Which one would you like to add to? ")
        int_response = int(response)
        count = 0
        # Make the comparison between the response and the particular title
        int_break = 0 # This will flag whether we've found the one we need
        for titles_iterator in self.titles:
            if int_response == count:
                # We need to check for the issue
                print "Second Which one would you like to add to?"
                new_count = 0
                for issue_iterator in titles_iterator.issuesheld:
                    print "(", count,") ", issue_iterator.name
                issue_response = raw_input("?")
                int_issue_response = int(issue_response)
                #And now we need to loop round to compare choice with objects
                issue_count = 0
                for issue_iterator in titles_iterator.issuesheld:
                    if int_issue_response == issue_count:
                        issue_iterator.addnewarticle() ######## We're gonna add an article to this issue
                        # Somehow I need to jump out of this loop
                        # otherwise the program continues with the second which one would you like
                        break # My first ever use of break! 
                    else:
                        issue_count = issue_count + 1
                break # This break is vital - if we don't have it, we loop round looking for more issues    
            else:
                count = count + 1
        print "I didn't understand your response! Please try again"
        #self.add_to_existing_issue()

    def deletearticle(self):
        #self.name = articlename
        #def search(self):
        """This will cycle through all instances of articles to find a keyword"""
        my_find = -1 # Assume  that nothing has been found
        searchfor = raw_input("""What article would you like to delete? (type a search term)  """)
        print
        print
        collect_results = [ ] # A list to hold all the possible results
        count = 1
        for collection_iter in self.titles:
            title_object = collection_iter
            for title_iter in title_object.issuesheld:
                issue_object = title_iter
                for issue_iter in issue_object.articlesheld:
                    article_object = issue_iter
                    #print article_object.name
                    name_find = article_object.name.find(searchfor)
                    author_find = article_object.author.find(searchfor)
                    keywords_find = article_object.keywords.find(searchfor)
                    if name_find <> -1 or my_find <> -1 or author_find <> -1 or keywords_find <> -1:
                        print "Search result", count, ":", article_object.name #, "my_find = ", my_find
                        print "Magazine Title:  ", title_object.name
                        print "Issue:           ", issue_object.name
                        print "Front cover:     ", issue_object.cover
                        print
                        print
                        #result = [article_object.name, title_object.name, issue_object.name, issue_object.cover] 
                        #result = [count, article_object.name, collection_iter.name, title_iter.name, issue_iter.name, issue_iter.author, issue_iter.keywords] # - I think we only need to collect the article_object.name here - let's see 
                        result = article_object.name
                        count = count+1

                        # This list stores all the information for that result
                        # print result # - debug
                        collect_results.append(result)
                        # Lets store this result within the collection of results                   

        for i in collect_results:
            print i

        # Let's check if anything wasn't found
        if count == 1:
            print "No articles were found with that search term - please try again"
            self.collectionmenu()

        else: # OK - we found something - let the user choose 
            delete_choice = raw_input ("Which one would like you to delete? - 0 to escape")
            del_choice_int = int(delete_choice)
            if del_choice_int == 0: # The use wants to exit
                self.collectionmenu()
            elif del_choice_int > count: # The user chose a number larger than the options
                print "That number is greater than the choices, please try again"
            else:
                confirm = raw_input("Do you really want to delete this? (y/n)")
                if confirm == "y":
                    # This is the key bit - I need a command here which deletes that object
                    #__del__(collect_results[del_choice_int[issue_iter]])
                    del_choice_int = del_choice_int -1
                    candidate_to_delete = collect_results[del_choice_int]
                    print candidate_to_delete
                    candidate_to_delete.__del__
                    # I think I'm going to need to iterate through the object and delete the article - this shouldn't be too difficult, as I have all the information in the results list.
                    self.collectionmenu()
                else: # They don't want to delete - return them to the main menu
                    print "You chose not to delete"
                    self.collectionmenu()

    def collectionmenu(self):
        """The user has chosen to create a new collection.
        This is their initial choice set. """
        print ''' 
    #################################################
    # Collection Menu                               #
    #################################################
    
    
    What would you like to do? 
        (a)  Add a new magazine
        (c)  Add an issue to a title
    (ac) Add an article to an already entered issue
    (d) Delete an article
        (l)  Leave this collection and go back to main menu
        '''
        response = raw_input()
        if response == "a":
            self.newtitle()
            self.collectionmenu() # Return back to this menu
        elif response == "c":
            self.choosetitle()
        elif response =="ac":
            self.add_to_existing_issue()
        elif response =="d":
            print "instance of self is ", self # Debug
            self.deletearticle() # OK - this is it - we're going to delete stuff! 
            self.collectionmenu() # Return back to this menu
        elif response == "l":
            confirm = raw_input("Really leave? (y/n)") # Confirm of quit
            if confirm == "y":
                #self.savecollection()#  - don't save it here - it could be an issue
                # Need to call the save from the level above
                return() ##### This is causing a problem when adding a new article after already adding one
        elif confirm == "n":
            self.collectionmenu() # Loop back to the mainmenu
        else:
            print ("I didn't recognise your response")
            self.collectionmenu()

    def changename():
        """This will allow you to change the name of any particular object.
        """
        newname = raw_input("What would you like the issue to be called?")
        self.name = newname
    # This is the last line of the Class MagazineCollection

class Title(MagazineCollection):
    """This class is a subclass of Magazine Collection and holds
    the title of a magazine - in my example, Linux Format. Any
    instances of this class takes it's name from the newtitle method.
    """
    def __init__(self, titlename):
        self.name = titlename
        self.issuesheld = [ ] # Will hold the issue objects

    def viewissue(self):
        iter = 1
        for i in self.issuesheld:
            print iter, "Issue: ", i.name
        self.titlemenu()

    def titlemenu(self):
        print """
    #########################################
    # Title Menu                            #
    #########################################
    
    
    What would you like to do?
        (a) Add an issue to this title
        (v) View issues that this title has
        (d) Delete issues that this title has (not yet implemented)
        """
        response = raw_input()
        if response == "a":
            self.addissue()
        elif response == "v":
            self.viewissue()
        #elif response == "d": # Must go on the TODO list
        #    self.deleteissue()
        else:
            print "I'm sorry, I didn't understand your response - please try again"
            self.titlemenu()

    def savetitle(self):
        """This function will save the collection as a set of objects"""
        file = raw_input("What is the filename?")

        myfile  = open(file, 'w')


        pickle.dump(self, myfile) # This self seems to refer to an Issue 
        # this is abug - it should be a collection
        print "finished"

    def addissue(self):
        """This will add an issue to the Title object
        """
        # They've selected this from the MagazineCollection class and have
        # selected a title object  
        print "you chose ", self.name
        issuename = raw_input("What is the date of the issue?")
        issuecover = raw_input("What is on the cover?")
        issueinstance = Issue(issuename, issuecover)
        self.issuesheld.append(issueinstance)
        # self.collectionmenu()
        # OK - this will have to create a list for each of the Titles and
        # add Issue objects into that list - issues will eventually
        # hold articles

class Issue(Title):
    """This class will create the instance of each issue - issues will hold 
    articles which will contain the data. 
    """
    def __init__(self,issuename, issuecover):
        self.name = issuename
        self.articlesheld = [ ] # Will hold all the articles for this issue
        self.cover = issuecover # What appears on the cover - this is handy for 
        # finding issues
        #print "On this cover you will find..>", self.cover
        self.issuemenu() # The user has created a new issue (from Title) 
        #- now time to take them to the issue menu

    def issuechangename():
        print "The issue is currently entitled:", self.name
        self.changename()

    def addnewarticle(self):
        newarticle = raw_input("What is the title of the article?")
        aninstance = Article(newarticle)
        self.articlesheld.append(aninstance)
        self.issuemenu()

    def viewarticles(self):
        for i in self.articlesheld:
            print i.name
        self.issuemenu()

    def issuemenu(self):
        print """
    #################################
    # Issues Menu                   #
    #################################
    
    What would you like to do?
        (a) add article
        (v) view articles
        (d) delete an article
        
        (g) Go back to Magazine menu
        """ # (d) delete article - not yet implemented - although it has started
        response = raw_input()
        if response == "a":
            self.addnewarticle()
        elif response == "v":
            self.viewarticles()
        elif response == "d":
            self.deletearticles()
        elif response =="g":
            #self.collectionmenu() # Go back to a higher level of menu
            # Currently throws an error due to this menu expecting a Title object
            # rather than an issue object. 
            return() # Using a break here instead of the code above takes you back to entry menu

        else:
            print "I did not understand your response"
            self.issuemenu()

class Article(Issue):
    """This is a subclass of Issue and creates the actual article information."""

    def __init__(self, articlename):
        self.name = articlename
        articleinformation = [ ]
        self.collectinformation(articleinformation)

    def __del__(self):
        print "Deleted"
        return()

    def collectinformation(self, articleinformation):
        self.author = raw_input("What is the name of the author?")
        self.keywords = raw_input("Any keywords?")
        #articleinformation.append(author)
        #articleinformation.append(keywords)

def savecollection(aninstance):
    """This will save the collection and all it's sub objects"""
    filename = raw_input("Filename?")
    file = open (filename, 'w')
    pickle.dump(aninstance, file)
    print "Finished"
    return()

def newcollection():
    """New collection was called from the first menu that the
    user was presented with. 
    """
    name = raw_input("What is the collection of magazines called?")
    aninstance = MagazineCollection(name)
    aninstance.collectionmenu() # This calls the method for the menu for this 
    # magazine collection object.    
    # I've returned now - and does the user want to save? 
    #print "Ok, I've returned and self is ", aninstance.name
    print "The object's name is ", aninstance.name
    save(aninstance)

def save(aninstance):
    response = raw_input("Do you want to save y/n?")
    if response == "n":
        mainmenu()
    elif response == "y":
        savecollection(aninstance)
        mainmenu()
    else:
        print "I didn't understand your response - please try again"
        save(aninstance)

def search(instance):
    print "you chose to search", instance.name
def edit(instance):
    print "you chose to edit", instance.name

def edit_or_search(aninstance):
    """This function determines whether the user searches or edits"""
    response = raw_input("""Would you like to edit or search?
    (e) Edit
    (s) Search
    (h) Export to HTML
    (sa) Save data
    (x) Exit program
    """)
    if response == "e":
        aninstance.collectionmenu()
        save(aninstance)
    elif response == "s":
        aninstance.search()
        mainmenu()
    elif response == "h":
        aninstance.export_to_html()
    elif response =="x":
        sure = raw_input("Are you sure?(y/n)")
        if sure == "y":
            print "bye"
            sys.exit(0)
    elif response == "sa":
        save(aninstance)
    else:
        print "Your response was not recognised - please try again"
    edit_or_search(aninstance)


def loadcollection():
    """This function will load a file which is a cpickled object of a 
    magazine collection. 
    """
    filename = raw_input("What is the filename?")
    try:

        file = open (filename, 'r')
    except IOError:
        print "That file was not found!"
        loadcollection()
    instance = pickle.load(file)
    #instance.collectionmenu() This used to take you to the instances menu
    # but now we are going to a new edit/search menu  
    edit_or_search(instance)

def load_existing_collection(file):
    print type (file)
    print file
    try:
        openfile = open(file, 'r')
        instance = pickle.load(openfile)
        edit_or_search(instance)
    except IOError:
        print "That file doesn't exist, please try again"
        loadcollection()
    #instance = pickle.load(openfile)
    #edit_or_search(instance)


def mainmenu():
    """This method is the main entry point method that the user will first see.
    It asks them whether they want to create a new collection, or load 
    an existing one"""

    if len(sys.argv) >1:
        filepassed = str(sys.argv[1])

        load_existing_collection(filepassed)


    else:
        print '''
    ###################################
    #  Main Menu                      #
    ###################################


    What would you like to do? 
    (n) Create a new collection
    (l) Load a collection
    (e) Exit program
        '''
        response = raw_input()
        if response == "n":
            newcollection()
        elif response == "l":
            loadcollection()
    #elif response == "s": 
    #    if aninstance: # Does a collection exist already for saving?
    #        savecollection(aninstance)
    #    else: 
    #        print"Sorry - you don't have anything to save"
        elif response =="e":
            confirm = raw_input("Really quit? (y/n)") # Confirm of quit
            if confirm == "y":
                sys.exit(0)
            elif confirm == "n":
                mainmenu() # Loop back to the mainmenu
            else:
                print "I'm sorry I didn't understand your response"
                mainmenu()
        else:
            print "I didn't recognise your response, please try again"
            mainmenu()

mainmenu() # Kicks the whole thing off

# #############################################################
#                                                             #
#                Documentation                                #
#                                                             #
# #############################################################


# When items in the list are resolved, they are moved to the changelog to show that
# that issue has been addressed - this is kind of satisfying and is a main 
# motivator

# Couple of outstanding issues with this program:

# TODO


# Bug - when the collectionmenu is spawned more than once, leaving it proves difficult
# View by particular groups (magazine, issue, titles etc.)
# Create media package to avoid the large download every time. 
# Create a media package to avoid downloads
# Create a help module which opens up a browser with html content
# Have to fix deletearticles as I now have an unwanted article in the file
# Bug - When adding a new magazine title, the user has to add an article
# Bug arises when choosing integer for choice, if no answer is given at all
# Bug -once you leave this issue, it asks you to save, but then throws back to 
# edit-or-save  function ... the only way to exit is to export
# this might not actually be a bug if I put an exit at that menu - you've
# already specified what file you want to work with. 
# Edit data? No provision for this yet
# Sorting on certain data sets?
# Date validation for issue dates
# Exporting to HTML jumps straight out of the program
# Current Search is jumping out of program on exit
# Delete articles
# When asking for collection, input is on same line as query
# Use cpickle instead of pickle
# Add more meta-information to html export

# Changelog

# Now adds a number to each article when exporting to html
# How about passing filename as a command line parameter to start the whole shebang?
# Develop function to view everything and possibly export to HTML
# Bug - when entering articles and then pressing 'g' to go back, then adding another issue
# a bug is filed - AttributeError: Issue instance has no attribute 'titles'
# Need to be able to add articles to already added issues
# Need to break out of loop in add_to_existing_issue
# Spelling on view articles on titles menu (at least I think it's titles menu)
# Program now allows saving if loaded at beginning
# Search now jumps back to the mainmenu
# Search searches all data of an article now.
# Choosing which title is now correct regardless
# Bug - search doesn't seem to be working now