#!/usr/bin/python
# -*- coding: utf8 -*-

# This file is part of the Chakra Project
#   Handles configuration file
#
#   Copyright (C) 2011 Lisa Vitolo <shainer@chakra-project.org>

#   This program 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.

# Errors regarding sections, e.g. the user tried to add a new section
# which already exists.
class SectionError(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return self.value

# Represents each line of the configuration file.
# Note that in the internal representation of keys and values
# blank spaces around keys and values are kept to mantain the
# formatting the user chose.
# getKey() and getValue() return the "pure" string for comparisons.
class ConfigLine:
    def __init__(self, line=0, comment="", section="", key="", value=""):
        self.comment = comment
        self.section = section
        self.key = key
        self.value = value
        
        self.line = line
    
    def isSectionLine(self):
        '''Returns True if line is in form [section]'''
        return self.key == "" and self.value == "" and self.section != ""
    
    def isCommentLine(self):
        '''Returns True if line is formed by a comment only'''
        return self.comment != "" and self.key == "" and self.value == ""
    
    def isEmptyLine(self):
        '''Returns True if line is empty'''
        return self.comment == "" and self.section == "" and self.key == "" and self.value == ""
    
    def isEmptyKey(self):
        '''Returns True if line has a key without any value associated'''
        return self.key != "" and self.value == ""
   
    def isKeyLine(self):
        '''Returns True if line is a general key line'''
        return self.key != ""
        
    def getKey(self):
        key = self.key.lstrip()
        key = key.rstrip()
        return key
    
    def getValue(self):
        value = self.value.lstrip()
        value = value.rstrip()
        return value
        
    def pair(self):
        k = self.getKey()
        v = self.getValue()
        return (k, v)
    
    # TODO: kept only for debugging reasons, will be eliminated
    def __str__(self):
        return str(self.line) + " section: " + self.section + ", " + self.key + "=" + self.value + self.comment

class ConfigManager:
    def __init__(self, config):
        '''Parses all the file and stores the contents in a dictionary
        with the section as key (an empty string for comment lines)'''
        self.config = config
        self.configfile = None
        
        try:
            self.configfile = open(self.config, "r")
        except IOError as err:
            print err
            exit(-1)
        
        self.parsedFile = {}
        self.parsedFile[""] = []
        
        self.lastline = 1
        currentSection = ""
        
        for lineNotStripped in self.configfile.readlines():
            lineNotStripped = lineNotStripped.rstrip() # this is meaningless
            line = lineNotStripped.lstrip()
            if len(line) == 0:
                emptyCfgLine = ConfigLine(self.lastline)
                self.parsedFile[""].append(emptyCfgLine)
                self.lastline += 1
                continue
            
            comment = ""
            key = ""
            value = ""
            
            if line[0] == "#":
                comment = lineNotStripped # keeps spaces at the beginning
            else:
                # Find inline comments and stores them
                withcomments = lineNotStripped.split("#")
                cfgline = withcomments[0]
                cfgline = cfgline.rstrip()
                
                if len(withcomments) > 1:
                    comment = withcomments[1]

                # New section found
                if cfgline.find("[") != -1:
                    currentSection = cfgline[cfgline.find("[")+1:-1]
                    
                    if (not self.hasSection(currentSection)):
                        self.parsedFile[currentSection] = []
                        sectionobj = ConfigLine(self.lastline, comment, currentSection)
                        self.parsedFile[currentSection].append(sectionobj)
                    self.lastline += 1
                    continue
                    
                keyandvalue = cfgline.split("=")
                key = keyandvalue[0]
                
                if len(keyandvalue) > 1:
                    value = keyandvalue[1]

            lineobj = ConfigLine(self.lastline, comment, currentSection, key, value)

            if (currentSection == "") and not lineobj.isCommentLine():
                raise ValueError("Found configuration line outside section.")
            if lineobj.isCommentLine():
                lineobj.section = ""
                self.parsedFile[""].append(lineobj)
            else:
                self.parsedFile[currentSection].append(lineobj)
            self.lastline += 1
        
        self.configfile.close()
        
    def sections(self):
        '''Returns the list of sections'''
        sects = []        
        
        for section in self.parsedFile:
            if (section != ""):
                sects.append(section)
                
        return sects
    
    def addSection(self, name):
        '''Adds a new section with given name'''
        if self.hasSection(name):
            raise SectionError("Section already present")
        
        self.parsedFile[name] = []
        sectobj = ConfigLine(line=self.lastline, section=name)
        self.parsedFile[name].append(sectobj)
        self.lastline += 1
        
    def removeSection(self, name):
        '''Removes the given section'''
        if not self.hasSection(name):
            raise SectionError("Section doesn't exist")
            
        del self.parsedFile[name]
        
    def renameSection(self, oldname, name):
        '''Renames the given section'''
        if not self.hasSection(oldname):
            raise SectionError("Section doesn't exist")
            
        if self.hasSection(name):
            raise SectionError("New section name already exists")
        
        self.parsedFile[name] = []
        
        # Moves every option to new section and deletes old one
        for opt in self.parsedFile[oldname]:
            index = self.parsedFile[oldname].index(opt)
            self.parsedFile[oldname].remove(opt)
            opt.section = name
            self.parsedFile[oldname].append(opt)
        
        self.parsedFile[name] = self.parsedFile[oldname]
        del self.parsedFile[oldname]
    
    def hasSection(self, name):
        '''Returns True if section is present (default empty section isn't considered)'''
        return (not len(name) == 0) and self.parsedFile.has_key(name)
    
    def sectionEntries(self, section):
        '''Returns a list of option pairs in section'''
        if not self.hasSection(section):
            raise SectionError("Section not present")
        
        opts = []
        for opt in self.parsedFile[section]:
            if opt.isKeyLine():
                opts.append(opt.pair())
        return opts
        
    def items(self):
        '''Returns a list of all options'''
        options = []
        
        for section in self.parsedFile:
            if (section != ""):
                options.append((section,))
                for opt in self.parsedFile[section]:
                    if opt.isKeyLine():        
                        options.append(opt.pair())
                
        return options
        
    def hasOption(self, section, key):
        '''Looks for at least one occurrence of the given key'''
        if not self.hasSection(section):
            return False
            
        for opt in self.parsedFile[section]:
            if opt.getKey() == key:
                return True
                
        return False
        
    def get(self, section, key):
        '''Gets all the values associated with key'''
        if not self.hasSection(section):
            raise SectionError("Section not present")
        
        values = []
        for opt in self.parsedFile[section]:
            if opt.getKey() == key:
                values.append(opt.getValue())
                
        if len(values) == 0:
            raise KeyError("Key not present")
            
        return values

    def replace(self, section, key, oldvalue, value):
        '''Replaces the given option with a new value'''
        if not self.hasSection(section):
            raise SectionError("Section not present")
            
        for opt in self.parsedFile[section]:
            if opt.pair() == (key, oldvalue):
                index = self.parsedFile[section].index(opt)
                self.parsedFile[section].remove(opt)
                opt.value = value
                self.parsedFile[section].insert(index, opt)
                
                return
        
        raise ValueError("Key not present")
        
    def replaceAll(self, section, key, newvalue):
        '''Replaces all keys with the given value'''
        if not self.hasSection(section):
            raise SectionError("Section not present")
            
        count = 0
        
        for opt in self.parsedFile[section]:
            if opt.getKey() == key:
                index = self.parsedFile[section].index(opt)
                self.parsedFile[section].remove(opt)
                opt.value = newvalue
                self.parsedFile[section].insert(index, opt)
                count += 1
                
        if count == 0:
            raise KeyError("Key not present")
        
    def addOption(self, section, key, value):
        '''Add a new option to configuration'''
        if (not self.hasSection(section)):
            raise SectionError("Section not present")
            
        if (self.hasOption(section, key)):
            print "WARNING: key already exists: you're adding a duplicate."
        
        line = self.parsedFile[section][0].line + 1
        n = ConfigLine(line, "", section, key, value)
        self.updateLines(line)
        
        self.parsedFile[section].append(n)
        
    def unset(self, section, key, value):
        '''Deletes the given option'''
        if not self.hasSection(section):
            raise SectionError("Section not present")
            
        for opt in self.parsedFile[section]:
            if opt.pair() == (key, value):
                index = self.parsedFile[section].index(opt)
                del self.parsedFile[section][index]
                return
                
        raise ValueError("Option not present")
        
    def unsetAll(self, section, key):
        '''Deletes all occurrences of key in the section'''
        if not self.hasSection(section):
            raise SectionError("Section not present")
            
        toUnset = []
        
        for opt in self.parsedFile[section]:
            print opt.getKey()
            if opt.getKey() == key:
                toUnset.append(opt)
        
        for opt in toUnset:
            self.parsedFile[section].remove(opt)

    def updateLines(self, line):
        '''Utility method to move line numbers forward when a new line is added'''
        for sect in self.parsedFile:
            for element in self.parsedFile[sect]:
                if element.line >= line:
                    i = self.parsedFile[sect].index(element)
                    self.parsedFile[sect].remove(element)
                    element.line += 1
                    self.parsedFile[sect].insert(i, element)
                    
        self.lastline += 1
        
    def write(self):
        '''Write new configuration on file'''
        options = []
        
        for section in self.parsedFile:
            for element in self.parsedFile[section]:
                options.append(element)
        
        with open("Akabei.conf", "w") as configfp:
            for opt in sorted(options, key=lambda option: option.line):
                if opt.isSectionLine():
                    configfp.write("[" + opt.section + "] " + opt.comment)
                elif opt.isCommentLine():
                    configfp.write(opt.comment)
                elif opt.isEmptyLine():
                    configfp.write("\n")
                    continue
                elif opt.isEmptyKey():
                    configfp.write(opt.key)
                else:
                    configfp.write(opt.key + "=" + opt.value)
                    if (opt.comment != ""):
                        configfp.write(" #" + opt.comment)
                configfp.write("\n")

    # TODO: debug method to be removed      
    def __str__(self):
        string = ""        
        
        for section in self.parsedFile:
            for element in self.parsedFile[section]:
                string += str(element) + "\n"
                
        return string