# ===================================================================
# The contents of this file are dedicated to the public domain.  To
# the extent that dedication to the public domain is not available,
# everyone is granted a worldwide, perpetual, royalty-free,
# non-exclusive license to exercise all rights associated with the
# contents of this file for any purpose whatsoever.
# No rights are reserved.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# ===================================================================

'''
Created on Wed Sep 12 08:02:46 2012

Parses a file to find out which nemo boundary settings to use 

@author John Kazimierz Farey
@author James Harle
$Last commit on:$
'''

# pylint: disable=E1103
# pylint: disable=no-name-in-module

#External imports
from collections import OrderedDict
import os
import logging

class Setup(object):
    '''
    Invoke with a text file location, class init reads and deciphers variables.

    attribute <settings> is a dict holding all the vars.
    '''
    """ This class holds the settings information """
    def __init__(self, setfile):
        """ Constructor, reads the settings file and sets the dictionary with setting name/key and
        it's value """
        #Logging for class
        self.logger = logging.getLogger(__name__)
        self.filename = setfile
        if not setfile: # debug
            self.filename = '../data/namelist.bdy'
        self._load_settings()
        self.variable_info = self.variable_info_reader(self.filename+'.info')

    def refresh(self):
        """ Re loads the settings from file """
        self._load_settings()
        
    def _load_settings(self):
        """ Loads the settings from file """
        try:
            namelist = open(self.filename, 'r')
        except:
            self.logger.error("Cannot open the file:"+self.filename)
            raise
        data = namelist.readlines()
        # Dictionary of all the vars in the file and a seperate settings for boolean values
        self.settings, self.bool_settings = _assign(_trim(data))
        namelist.close()
                
    def _get_var_name_value(self, line):
        """ splits the line into key value pair. """
        key_value = line.split("=", 2)
        name_prefix = key_value[0][0:2].lower()
        name = key_value[0][3:].lower().strip() # 3 -> 0 to keep type info
        value_str = key_value[1].strip()
        index = '-1'
        value = ''
        if name_prefix == 'ln':
            if value_str.find('true') is not -1:
                value = True
            elif value_str.find('false') is not -1:
                value = False
            else:
                raise ValueError('Cannot assign %s to %s, must be boolean' %(value_str, name))

        elif name_prefix == 'rn' or name_prefix == 'nn':
            if value_str.find('.') > -1 or value_str.find('e') > -1:
                try:
                    value = float(value_str)
                except ValueError:
                    self.logger.error('Cannot assign %s to %s, must be float')
                    raise
            else:
                try:
                    value = int(value_str)
                except ValueError:
                    self.logger.error('Cannot assign %s to %s, must be integer')
                    raise
        elif name_prefix == 'cn' or name_prefix == 'sn':
            value = value_str.strip("'")
            if name == 'dst_dir':
                value = os.path.join(value, '')
        elif name_prefix == 'cl':
            name = key_value[0].split('(')
            index = name[1].split(')')
            name = name[0].strip()
            index = index[0].strip()
            value = value_str
        else:
            raise ValueError('%s data type is ambiguous' %key_value)
        return name, index, value

    def write(self):
        '''
        This method write backs the variable data back into the file
        '''
        try:
            namelist = open(self.filename, 'r')
        except:
            self.logger.error("Cannot open the file:"+self.filename+" to write ")
            raise
        data = namelist.readlines()

        for name in self.settings:
            values = self.settings[name]
            if type(values).__name__ != 'dict':
                values = {'-1': values}
            for index, value in values.iteritems():
                count = -1
                for line in data:
                    count = count + 1
                    #find the variable
                    line_without_comments = strip_comments(line)
                    if line_without_comments == '':
                        continue
                    data_name, data_index, data_value = self._get_var_name_value(line_without_comments)

                    if data_name == name:
                        #found the variable line
                        if data_index == index:
                            if type(data_value).__name__ == 'bool' \
                                and \
                               type(value).__name__ != 'bool':
                                data[count] = _replace_var_value(line, data_value,\
                                                                 self.bool_settings[name])
                                continue

                            if data_value == value:
                                break
                            else:
                                data[count] = _replace_var_value(line, data_value, value)
                                break
        namelist.close()
        namelist = open(self.filename, 'w')
        namelist.truncate()
        namelist.writelines(data)
        namelist.close()

    def variable_info_reader(self, filename):
        """ This method reads the variable description data from 'variable.info' file in the pynemo installation path
        if it can't find the file with the same name as input bdy file with extension .info
        Keyword arguments:
        filename -- filename of the variables information
        returns a dictionary with variable name and its description
        """
        variable_info = {}               
        if filename is None or not os.path.exists(filename):
            #Read the default file
            file_path, dummy = os.path.split(__file__)
            filename = os.path.join(file_path,'variable.info')
        try:
            namelist = open(filename, 'r')
            data = namelist.readlines()
            for line in data:
                name = _get_var_name(line)
                value = line.split("=", 1)[1]
                variable_info[name[0]] = value.strip()                           
        except IOError:
            self.logger.error("Cannot open the  variable file:"+filename)
        return variable_info
        
            
def _trim(data):
    """ Trims the sets of lines removing empty lines/whitespaces and removing comments
    which start with ! """
    newdata = []
    while data:
        line = data.pop(0)
        line = line.rsplit('!')[0].strip()
        if line is not '':
            newdata.append(line)
    return newdata

def _get_val(vars_dictionary, bool_vars_dictionary, line):
    """ traverses input string and appends the setting name and its value to dictionary
    of settings and also if the setting name holds a boolean value then to the dictionary
    of boolean variables. checks the type and raises error for ambiguous values"""

    logger = logging.getLogger(__name__)
    name_prefix = line[0][0:2].lower()
    name = line[0][3:].lower().strip() # 3 -> 0 to keep type info
    value = line[1].strip()

    if name_prefix == 'ln':
        if value.find('true') is not -1:
            if vars_dictionary.has_key(name) != True:
                vars_dictionary[name] = True
            bool_vars_dictionary[name] = True
        elif value.find('false') is not -1:
            if vars_dictionary.has_key(name) != True:
                vars_dictionary[name] = False
            bool_vars_dictionary[name] = False
        else:
            raise ValueError('Cannot assign %s to %s, must be boolean' %(value, name))

    elif name_prefix == 'rn' or name_prefix == 'nn':
        if value.find('.') > -1 or value.find('e') > -1:
            try:
                vars_dictionary[name] = float(value)
            except ValueError:
                logger.error('Cannot assign %s to %s, must be float')
                raise
        else:
            try:
                vars_dictionary[name] = int(value)
            except ValueError:
                logger.error('Cannot assign %s to %s, must be integer')
                raise
    elif name_prefix == 'cn' or name_prefix == 'sn':
        vars_dictionary[name] = value.strip("'")
        if name == 'dst_dir':
            vars_dictionary[name] = os.path.join(vars_dictionary[name], '')
    elif name_prefix == 'cl':
        name = line[0].split('(')
        index = name[1].split(')')
        if name[0].strip() not in vars_dictionary.keys():
            vars_dictionary[name[0].strip()] = {}
        vars_dictionary[name[0].strip()][index[0].strip()] = value.strip()
    else:
        raise ValueError('%s data type is ambiguous' %line)

def _replace_var_value(original_line, value, new_value):
    """ replaces the variable name value with new_value in the original_line"""
    if type(value).__name__ == 'bool':
        value = str(value).lower()
        new_value = str(new_value).lower()
    elif type(value).__name__ == 'str': #an empty string need to replace with ''
        if value == '':
            value = '\'\''
            new_value = '\''+new_value+'\''
    return original_line.replace(str(value), str(new_value), 1)

def _get_var_name(line):
    """ parses the line to find the name and if it is part of the array '()'
    then returns name of variable and index of the array. if variable is not
    array then only variable name and -1 in index"""
    name_value = line.split("=", 1)
    name_prefix = name_value[0][0:2].lower()
    name = name_value[0][3:].lower().strip() # 3 -> 0 to keep type info
    if name_prefix in ['ln', 'rn', 'nn', 'cn', 'sn']:
        return name, -1
    elif name_prefix == 'cl':
        name = name_value[0].split('(')
        index = name[1].split(')')
        return name[0], index[0]

    # Returns tidy dictionary of var names and values
def _assign(data):
    """ return a dictionary of variables and also special dictionary for boolean variable """
    vars_dictionary = OrderedDict({})
    bool_vars_dictionary = OrderedDict({})
    for line in data:
        keyvalue = line.split('=', 1)
        _get_val(vars_dictionary, bool_vars_dictionary, keyvalue)
    return vars_dictionary, bool_vars_dictionary

def strip_comments(line):
    """ strips the comments in the line. removes text after ! """
    line = line.rsplit('!')[0].strip()
    return line