"""a set of python tools for manipulating MSC Adams Drill files
"""
from os import environ
from os import remove
from os import rename
import os.path
import re
import subprocess
import thornpy
import jinja2

# Define regular expressions
TO_PARAMETER_PATTERN = re.compile('^ [_0-9a-zA-Z]+\\s+=\\s+((\'[-_0-9a-zA-Z<>\\\\/\\.]+\')|(-?[\\+-\\.e0-9]+))\\s*$')

env = jinja2.Environment(
    loader=jinja2.PackageLoader('adamspy.adripy', 'templates'),
    autoescape=jinja2.select_autoescape(['evt','str']),
    keep_trailing_newline=True,
    trim_blocks=True,
    lstrip_blocks=True
)

# Test that the user config file exists
if 'ADRILL_USER_CFG' not in os.environ:
    # raise EnvironmentError('ADRILL_USER_CFG environment variable is not set!')
    environ['ADRILL_USER_CFG'] = os.path.join(os.environ['USERPROFILE'], '.adrill.cfg')
elif not os.path.exists(os.environ['ADRILL_USER_CFG']):
    raise FileExistsError('The configuration file {} does not exist!  You must set the ADRILL_USER_CFG environment variable to an existing cfg file before importing adripy.'.format(os.environ['ADRILL_USER_CFG']))

# Test that the user shared file exists
if 'ADRILL_SHARED_CFG' not in os.environ:    
    if 'XDG_CACHE_HOME' in os.environ:
        environ['ADRILL_SHARED_CFG'] = os.path.join(os.environ['XDG_CACHE_HOME'], 'adrill', 'adrill.cfg')
    else:
        raise EnvironmentError('ADRILL_SHARED_CFG environment variable is not set!')
elif not os.path.exists(os.environ['ADRILL_SHARED_CFG']):
    raise FileExistsError('The configuration file {} does not exist!  You must set the ADRILL_SHARED_CFG environment variable to an existing cfg file before importing adripy.'.format(os.environ['ADRILL_SHARED_CFG']))

# Test that the adams launch file exists
if 'ADAMS_LAUNCH_COMMAND' not in os.environ:
    # raise EnvironmentError('ADAMS_LAUNCH_COMMAND environment variable is not set!')
    environ['ADAMS_LAUNCH_COMMAND'] = os.path.join(os.environ['XDG_CACHE_HOME'], 'common', 'mdi.bat')
elif not os.path.exists(os.environ['ADAMS_LAUNCH_COMMAND']):
    raise FileExistsError('The adams launch file {} does not exist!  You must set the ADRILL_SHARED_CFG environment variable to an existing cfg file before importing adripy.'.format(os.environ['ADAMS_LAUNCH_COMMAND']))

# Dictionary of TO tool length parameters
TO_LENGTH_PARAM = {}
TO_LENGTH_PARAM['accelerator'] = ['Accelerator_Length']
TO_LENGTH_PARAM['agitator'] = ['Power_Body_Length', 'Shock_Stub_Length']
TO_LENGTH_PARAM['blade_reamer'] = ['Reamer_Length']
TO_LENGTH_PARAM['crossover'] = ['Crossover_Length']
TO_LENGTH_PARAM['dart'] = ['Dart_Length']
TO_LENGTH_PARAM['drill_collar'] = ['Drillcollar_Length']
TO_LENGTH_PARAM['drillpipe'] = ['Pipe_Length']
TO_LENGTH_PARAM['flex_pipe'] = ['Flex_Length']
TO_LENGTH_PARAM['generic_long'] = ['Tool_Length']
TO_LENGTH_PARAM['generic_short'] = ['GenericShort_Length']
TO_LENGTH_PARAM['hw_pipe'] = ['Pipe_Length']
TO_LENGTH_PARAM['instrumentation_sub'] = ['ISUB_Length']
TO_LENGTH_PARAM['jar'] = ['Body_Length', 'Stub_Length']
TO_LENGTH_PARAM['mfr_tool'] = ['Tool_Length']
TO_LENGTH_PARAM['motor'] = ['Motor_Length']
TO_LENGTH_PARAM['mwd_tool'] = ['Tool_Length']
TO_LENGTH_PARAM['pdc_bit'] = ['Bit_Length']
TO_LENGTH_PARAM['single_point'] = ['Bit_Length']
TO_LENGTH_PARAM['roller_cone_bit'] = ['Bit_Length']
TO_LENGTH_PARAM['shock_sub'] = ['Installed_Length']
TO_LENGTH_PARAM['short_collar'] = ['Collar_Length']
TO_LENGTH_PARAM['stabilizer'] = ['Stabilizer_Length']

def turn_measure_on(string_file, tool_types=[], tool_numbers=[], tool_names=[]):
    """Modify a string file to turn measure on for the designated
    tools.  Tools may be designated by type, number
    (stack order), or name.

    Arguments:
        string_file {str}   -- Full path to an MSC Adams Drill 
                               string file including the .str
                               extension
    
    Keyword Arguments:
        tool_types {list}   -- List of tool type as seen in the
                               string file (e.g. pdc_bit, motor, 
                               stabilizer)
        tool_numbers {list} -- List of stack orders corresponding
                               to tools in a string
        tool_names {list}   -- List of tool names
    
    Returns:
        int                 -- number of tools measured
    """
    n = 0
    mark = False
    with open(string_file,'r') as fid_str, open(string_file.replace('.str','.tmp'),'w') as fid_str_tmp:
        for line in fid_str:
            if line.startswith('$'):
                fid_str_tmp.write(line)

            # If this is a measure line that has been marked
            elif mark and ' measure' in line.lower():
                fid_str_tmp.write(" Measure  =  'yes'\n")
                n += 1
                mark = False
                            
            # Mark if the tool matches a designated stack order
            elif ' stack_order' in line.lower():
                if int(line.replace(' ','').replace('\n','').split('=')[-1]) in tool_numbers:
                    mark = True
                fid_str_tmp.write(line)
            
            # Mark if the tool matches a designated tool type
            elif ' type' in line.lower():
                if line.replace(' ','').replace('\n','').split("'")[-2] in tool_types:
                    mark = True
                fid_str_tmp.write(line)
            
            # Mark if the tool matches a designated tool name
            elif ' name' in line.lower():
                if line.replace(' ','').replace('\n','').split("'")[-2] in tool_names:
                    mark = True
                fid_str_tmp.write(line)
            
            else:
                fid_str_tmp.write(line)

    remove(string_file)
    rename(string_file.replace('.str','.tmp'), string_file)
    return n


    """
    Return the name and file name of the nth tool of type 'tool_type'
    in 'string_file'.
    
    Parameters:
        string_file :      Full path to an MSC Adams Drill string file 
                        including the .str extension
        
        tool_type   :        Tool type as seen in the string file
                        (e.g. pdc_bit, motor, stabilizer)
        
        n :                If the string file has multiple tools of the requested
                        tool type, use this parameter to return the nth tool.
                        Default  is n=1.
        return_full_path : If true, removes the Adrill cdb name and replaces with
                        the adrill cdb file path.  Default is True
                  
    Returns:
        tool_name  :  Name of the requested tool
        tool_file  :  Full file path the the requested tool's property file
        tool_stack :  Stack order of tool
        group_name :  Tools group name if it has one
    """
def get_tool_name(string_file, tool_type, n=1, return_full_path=True):
    """Return the name and file name of the nth tool of type 'tool_type'
    in 'string_file'.
    
    Arguments:
        string_file {str}       -- Full path to an MSC Adams 
                                   Drill string file including
                                   the .str extension
        tool_type {str}         -- Tool type as seen in the
                                   string file (e.g. pdc_bit,
                                   motor, stabilizer)
    
    Keyword Arguments:
        n {int}                 -- If the string file has 
                                   multiple tools of the 
                                   requested tool type, use this
                                   parameter to return the nth 
                                   tool. (default: 1)
        return_full_path {bool} -- If true, removes the Adrill 
                                   cdb name and replaces with the
                                   adrill cdb file path.  
                                   (default: True)
    
    Raises:
        ValueError              -- Raised if tool of type 
                                   tool_type is not found in
                                   string_file

    Returns:
        str -- Name of the requested tool
        str -- Full file path the the requested tool's property file
        int -- Stack order of tool
        str -- Tools group name if it has one
    """

    tool_found = False
    fid = open(string_file,'r')
    if tool_type == 'hole':
        for line in fid:
            if ' Hole_Property_File  =  ' in line:
                tool_file = line.split("'")[1].replace('/','\\')
                tool_name = tool_file.split('\\')[-1].split('.')[0]
                group_name = tool_name
                if return_full_path:
                    tool_file = get_toolFilename_fullNotation(tool_file)
                tool_found = True
                stack_order = 0
                break
    else:
        count = 0
        for line in fid:
            if ' stack_order' in line.lower():
                stack_order = int(line.replace(' ','').replace('\n','').split('=')[-1])
            elif count == n and ' name ' in line.lower():
                group_name = line.split("'")[1]
            elif count == n and ' property_file' in line.lower():
                tool_file = line.split("'")[1].replace('/','\\')
                tool_name = tool_file.split('\\')[-1].split('.')[0]
                if return_full_path:
                    tool_file = get_toolFilename_fullNotation(tool_file)
                tool_found = True
                break
            elif ' type ' in line.lower() and tool_type.lower() in line.lower():
                count += 1
    
    if tool_found:
        return tool_name, tool_file, stack_order, group_name
    else:
        raise ValueError('Tool of type {} not found in {}'.format(tool_type, string_file))

def get_toolFilename_fullNotation(toolFilename_cdbNotation):
    if '<' in toolFilename_cdbNotation:
        cdb_name = toolFilename_cdbNotation.split('>')[0].replace('<','')
        cdbs = get_adrill_cdbs(os.environ['ADRILL_USER_CFG'], os.environ['ADRILL_SHARED_CFG'])
        if cdb_name in cdbs:
            cdb_path = cdbs[cdb_name]   
            toolFilename_fullNotation = cdb_path + toolFilename_cdbNotation.split('>')[1]                 
        else:
            raise cdbError('ADrill Database {} not defined.'.format(cdb_name))
    else:
        toolFilename_fullNotation = toolFilename_cdbNotation
    return toolFilename_fullNotation


def get_adrill_cdbs(adrill_user_cfg, adrill_shared_cfg=None):
    """
    Return the names and locations of all user defined MSC Adams Drill
    configuration databases (cdbs)
    
    Parameters:
        adrill_user_cfg : Full path to an MSC Adams Drill user configuration
                        file.  This should be in the HOME directory
                  
    Returns:
        cdbs :  A dictionary with the cdb names as keys and cdb 
                locations as values.
    """
    cdbs = {}
    with open(adrill_user_cfg,'r') as fid:
        for line in fid:
            if line.startswith('DATABASE'):
                # try:
                cdb_name = re.split('[\t ]+',line.lstrip())[1]
                cdb_loc = os.path.normpath(re.split('[\t ]+', line, maxsplit=2)[-1].replace('\n','').replace('$HOME',environ['USERPROFILE']))
                cdbs[cdb_name] = cdb_loc
                # except:
                #     raise cdbError('The following line in {} could not be interpreted.\n\n{}'.format(adrill_user_cfg,line))
    if adrill_shared_cfg:
        top_dir = os.path.split(adrill_shared_cfg)[0]
        with open(adrill_shared_cfg,'r') as fid:
            for line in fid:
                if line.startswith('DATABASE'):
                    # try:
                    cdb_name = re.split('[\t ]+', line, maxsplit=2)[1]
                    cdb_loc = os.path.normpath(re.split('[\t ]+', line, maxsplit=2)[-1].replace('\n','').replace('$HOME',environ['USERPROFILE']).replace('$topdir',top_dir))                        
                    cdbs[cdb_name] = cdb_loc
                    # except:
                        # raise cdbError('The following line in {} could not be interpreted.\n\n{}'.format(adrill_shared_cfg,line))
    return cdbs

def get_TO_param(filename, requested_parameter):
    """
    Return the value of a parameter in a tiem orbit file
    
    Parameters:
        filename            :    Full path to a tiem orbit file
        requested_parameter :   Name of a parameter in TO_file  
                  
    Returns:
        requested_value :  The value assigned to TO_param in TO_file
    """
    # Check if CDB notation used and Convert
    filename = get_toolFilename_fullNotation(filename)

    # Initialize a flag indicating that the parameter has not yet
    # been found
    param_found = False
    
    # Read in the tiem orbit file
    with open(filename, 'r') as fid:
        lines = fid.readlines()

    for line in lines:
        # For each line in the file, check if we are at a
        # parameter line
        if TO_PARAMETER_PATTERN.match(line):
            # If we're at a parameter line, check if it's the
            # requested parameter.
            [parameter, value] = re.sub('[\\s\\n]','',line).split('=')

            if parameter.lower() == requested_parameter.lower():
                # If we're at the requested parameter line,
                # check if it's an adams string or a number        
                
                if "'" in value:
                    # If value is an adams string
                    requested_value = value.replace("'",'')
                
                else:
                    # If value is a number
                    requested_value = int(value) if thornpy.numtype.str_is_int(value) else float(value)
                
                # Mark the parameter as found
                param_found = True
                break

    if param_found:
        return requested_value
    else:
        raise ValueError('{} does not contain the parameter {}'.format(filename,requested_parameter))


def has_tool(string_file, tool_type):
    """
    Returns true if string_file has at least one tool of type tool_type
    
    Parameters:
        string_file : Full path to an Adams Drill string file
        tool_type :    Adams Drill tool type
                  
    Returns:
        tool_type_found: True if string_file contains at least one tool of type tool_type
    """
    tool_type_found = False
    fid = open(string_file,'r')
    for line in fid:
        if ' Type  =  ' in line and tool_type in line:
            tool_type_found = True
            break
    fid.close()
    return tool_type_found

def fullNotation_to_cdbNotation(string_file):
    """
    Replaces all references in a string file that use full path notation to use CDB notation
    
    Parameters:
        string_file :   Full path to an Adams Drill string file
                  
    Returns:
        n           :   Num
    """

    cdbs = get_adrill_cdbs(os.environ['ADRILL_USER_CFG'], os.environ['ADRILL_SHARED_CFG'])
    n = 0 

    with open(string_file, 'r') as fid_str, open(string_file.replace('.str','.tmp'), 'w') as fid_str_tmp:
        for line in fid_str:
            cdb_found = False
            if 'property_file' in line.lower():
                for cdb_name in cdbs:
                    if cdbs[cdb_name] in line:
                        cdb_found = True
                        new_line = line.replace(cdbs[cdb_name], '<{}>'.format(cdb_name))
                        n += 1

                    elif cdbs[cdb_name].replace('\\','/') in line:
                        cdb_found = True
                        new_line = line.replace(cdbs[cdb_name].replace('\\','/'), '<{}>'.format(cdb_name))
                        n += 1

                    elif cdbs[cdb_name].replace('/','\\') in line:
                        cdb_found = True
                        new_line = line.replace(cdbs[cdb_name].replace('/','\\'), '<{}>'.format(cdb_name))
                        n += 1

            if cdb_found:
                fid_str_tmp.write(new_line)
            else:
                fid_str_tmp.write(line)
    
    remove(string_file)
    rename(string_file.replace('.str','.tmp'), string_file)

    return n

def cdbNotation_to_fullNotation(string_file):
    """
    Replaces all references in a string file that use CDB notation to use full path notation
    
    Parameters:
        string_file :   Full path to an Adams Drill string file
                  
    Returns:
        n           :   Number of replacements that were made.
    """

    cdbs = get_adrill_cdbs(os.environ['ADRILL_USER_CFG'], os.environ['ADRILL_SHARED_CFG'])
    n = 0 

    with open(string_file, 'r') as fid_str, open(string_file.replace('.str','.tmp'), 'w') as fid_str_tmp:
        for line in fid_str:
            cdb_found = False
            if 'property_file' in line.lower():
                for cdb_name in cdbs:
                    if '<{}>'.format(cdb_name) in line:
                        cdb_found = True
                        new_line = line.replace('<{}>'.format(cdb_name), cdbs[cdb_name].replace('\\','/'))
                        n += 1

            if cdb_found:
                fid_str_tmp.write(new_line)
            else:
                fid_str_tmp.write(line)
    
    remove(string_file)
    rename(string_file.replace('.str','.tmp'), string_file)
    
    return n

def get_cdb_path(full_filepath):    
    """
    Given the full path to a file located in a cdb, get_cdb_path returns the path to a
    file with the cdb path replaced by the cdb alias.  full_filepath will be returned if
    no cdb is found in the path.

    Parameters:
        full_filepath   :   Full file path to a file in a cdb
                  
    Returns:
        cdb_filepath    :   Path to a file with the cdb path replaced by the cdb alias.
    """
    cdb_filepath = full_filepath
    cdbs = get_adrill_cdbs(os.environ['ADRILL_USER_CFG'], os.environ['ADRILL_SHARED_CFG'])
    for cdb_name in cdbs:
        if cdbs[cdb_name] in full_filepath:
            cdb_filepath = full_filepath.replace(cdbs[cdb_name], '<{}>'.format(cdb_name))
        elif cdbs[cdb_name].replace('\\','/') in full_filepath:
            cdb_filepath = full_filepath.replace(cdbs[cdb_name].replace('\\','/'), '<{}>'.format(cdb_name))
        elif cdbs[cdb_name].replace('/','\\') in full_filepath:
            cdb_filepath = full_filepath.replace(cdbs[cdb_name].replace('/','\\'), '<{}>'.format(cdb_name))
    return cdb_filepath

def get_full_path(cdb_filepath):    
    """
    Given the cdb path to a file located in a cdb, get_full_path returns the path to a
    file with the cdb alias replaced by the cdb location.

    Parameters:
        cdb_filepath    :   Full file path to a file in a cdb
                  
    Returns:
        full_filepath   :   Path to a file with the cdb path replaced by the cdb alias.
    """    
    # Find a string that looks like a database alias
    match = re.search('^<.+>', cdb_filepath)
    
    # Return the given filepath if filepath looks like a full filepath
    if match is None:
        return cdb_filepath
    
    # Pull the database name out of the group
    cdb_name = match.group(0).replace('<', '').replace('>', '')

    # Get a dictionar of the known cdbs
    cdbs = get_adrill_cdbs(os.environ['ADRILL_USER_CFG'], os.environ['ADRILL_SHARED_CFG'])
    
    # Raise an error if cdb_name is not in the cdbs dictionary
    if cdb_name not in cdbs:
        raise ValueError('{} not in {} OR {}!'.format(cdb_name, os.environ['ADRILL_USER_CFG'], os.environ['ADRILL_SHARED_CFG']))
    
    full_filepath = cdb_filepath.replace(match.group(0), cdbs[cdb_name])

    return full_filepath

def get_cdb_location(cdb_name):
    """
    Returns the location of cdb 'cdb_name'

    Parameters:
        cdb_name        :   Alias of a cdb
                  
    Returns:
        cdb_location    :   Location of cdb
    """
    cdbs = get_adrill_cdbs(os.environ['ADRILL_USER_CFG'], os.environ['ADRILL_SHARED_CFG'])
    return cdbs[cdb_name]

def replace_tool(string_file, old_tool_file, new_tool_file, old_tool_name='', new_tool_name='', N=0):
    """
    Swaps old_tool_file for new_tool_file in string_file.  Also replaces the tools Name field.
    
    Parameters:
        string_file     :   Full path to an Adams Drill string 
                            file
        old_tool_file   :   Path to an Adams Drill tool property
                            file that exists in string_file. May
                            use full path or Adrill CDB notation.
        new_tool_file   :   Path to an Adams Drill tool property
                            file to replace old_tool_file. May
                            use full path or Adrill CDB notation.
        N               :   Number of replacements to make.
                            Default is 0 which will replace all
                            instances.
        old_tool_name   :   Name of the tool to replace. Default
                            is the filename.
        new_tool_name   :   Name of the new tool.  Default is the
                            filename.
                  
    Returns:
        n               :   Number of replacements that were made
    """
    old_tool_file = old_tool_file.replace('\\','/')
    new_tool_file = new_tool_file.replace('\\','/')
    
    cdbs = get_adrill_cdbs(os.environ['ADRILL_USER_CFG'], os.environ['ADRILL_SHARED_CFG'])
    
    # Get cdb associated with old_tool_file
    old_tool_has_cdb = False
    for cdb_name in cdbs:
        if '<{}>'.format(cdb_name) in old_tool_file or cdbs[cdb_name].replace('\\','/') in old_tool_file:
            old_cdb_name = cdb_name
            old_cdb_loc = cdbs[cdb_name].replace('\\','/')
            old_tool_has_cdb = True
            break
    
    # If old_tool_file uses cdb notation then change it to full path notation
    if '<' in old_tool_file:
        if old_tool_has_cdb:
            old_tool_file = old_tool_file.replace('<{}>'.format(old_cdb_name), old_cdb_loc)
        else:
            raise cdbError('ADrill Database {} not defined.'.format(cdb_name))
                
    # Get cdb associated with new_tool_file
    new_tool_has_cdb = False
    for cdb_name in cdbs:
        if '<{}>'.format(cdb_name) in new_tool_file or cdbs[cdb_name].replace('\\','/') in new_tool_file:
            new_cdb_name = cdb_name
            new_cdb_loc = cdbs[cdb_name].replace('\\','/')
            new_tool_has_cdb = True
            break
    
    # If new_tool_file uses cdb notation then change it to full path notation
    if '<' in new_tool_file:
        if new_tool_has_cdb:
            new_tool_file = new_tool_file.replace('<{}>'.format(new_cdb_name), new_cdb_loc)
        else:
            raise cdbError('ADrill Database {} not defined.'.format(cdb_name))

    if old_tool_name == '':
        old_tool_name = old_tool_file.split('/')[-1].split('.')[0]
    if new_tool_name == '':
        new_tool_name = new_tool_file.split('/')[-1].split('.')[0]
    
    # Open the original string file for reading and a new string file for writing
    fid_oldString = open(string_file,'r')
    fid_newString = open(string_file.replace('.str','.tmp'),'w')

    # Initiate the number of replacements made
    n = 0

    # If the tool is a hole
    if old_tool_file.endswith('.hol'):
        # Loop through the string file to find the hole property file line
        for line in fid_oldString:
            if ' Hole_Property_File  =  ' in line:
                if '<' in line and new_tool_has_cdb:
                    for cdb_name in cdbs:
                        if cdbs[cdb_name].replace('\\','/') in new_tool_file:
                            new_cdb_name = cdb_name
                            new_cdb_loc = cdbs[cdb_name].replace('\\','/')
                            break
                    new_tool_file = new_tool_file.replace(new_cdb_loc, '<' + new_cdb_name + '>')
                fid_newString.write(" Hole_Property_File  =  '{}'\n".format(new_tool_file))
                n += 1
            else:
                fid_newString.write(line)
    
    # If the tool is not a hole
    else:
        # Loop through the string file to find and replace the corresponding tool block
        replace = False
        for line in fid_oldString:
            if ' Type' in line and not line.startswith('$'):
                tool_type = line.split("'")[1]
                fid_newString.write(line)
            elif ' Stack_Order' in line and not line.startswith('$'):
                stack_order = int(line.replace(' ','').replace('\n','').split('=')[1])
                fid_newString.write(line)
            elif " Name  =  '{}".format(old_tool_name) in line and (n<N or N==0):
                if " Name  =  '{}_{:02d}'".format(old_tool_name,stack_order) in line:
                    fid_newString.write(" Name  =  '{}_{:02d}'\n".format(new_tool_name, stack_order))
                else:
                    fid_newString.write(" Name  =  '{}'\n".format(new_tool_name))
                replace = True
            elif ' Property_File' in line and replace and not line.startswith('$'):
                # Check if line uses cdb notation and replace file path with cdb name
                if '<' in line and new_tool_has_cdb:
                    for cdb_name in cdbs:
                        if cdbs[cdb_name].replace('\\','/') in new_tool_file:
                            new_cdb_name = cdb_name
                            new_cdb_loc = cdbs[cdb_name].replace('\\','/')
                            break
                    new_tool_file = new_tool_file.replace(new_cdb_loc, '<' + new_cdb_name + '>')
                fid_newString.write(" Property_File  =  '{}'\n".format(new_tool_file))
                replace = False
                n += 1
            else:
                fid_newString.write(line)

    # Close the string files, delete the original one, and rename the new one
    fid_oldString.close()
    fid_newString.close()
    remove(string_file)
    rename(string_file.replace('.str','.tmp'), string_file)

    return n

def get_string_length(string_file):
    """
    Gets the total length of the drill string defined in string_file
    
    Parameters:
        string_file     :   Full path to an Adams Drill string
                            file
                      
    Returns:
        string_length   :   Cumulative length of the string    
    """
    cdbs = get_adrill_cdbs(os.environ['ADRILL_USER_CFG'], os.environ['ADRILL_SHARED_CFG'])
    # print(cdbs)
    tool_lengths = []
    fid_string = open(string_file, 'r')
    for line in fid_string:
        if ' property_file' in line.lower() and not line.startswith('$'):
            tool_file = line.split("'")[1].replace('/', '\\')
            if '<' in tool_file:
                # Get cdb associated with tool_file
                tool_has_cdb = False
                for cdb_name in cdbs:
                    if '<{}>'.format(cdb_name) in tool_file:
                        tool_cdb_name = cdb_name
                        cdb_loc = cdbs[cdb_name].replace('/','\\')
                        tool_has_cdb = True
                        break  
                # Change cdb notation to full path notation
                if tool_has_cdb:
                    tool_file = tool_file.replace('<{}>'.format(tool_cdb_name), cdb_loc)
                else:
                    raise cdbError('ADrill Database {} not defined.'.format(cdb_name))
            fid_tool = open(tool_file, 'r')
            file_type = ''
            for line in fid_tool:
                if file_type and 'top_drive' not in file_type.lower() and line.replace(' ', '').split('=')[0] in TO_LENGTH_PARAM[file_type] and not line.startswith('$'):
                    tool_length = float(line.replace(' ', '').split('=')[1])
                    tool_lengths.append(tool_length)
                    break
                elif ' file_type' in line.lower() and not line.startswith('$'):
                    file_type = line.replace(' ', '').replace("'",'').replace('\n','').split('=')[1]
            fid_tool.close()
        if ' number_of_joints' in line.lower():
            n = int(line.replace(' ','').split('=')[1])
            tool_lengths[-1] = tool_lengths[-1]*n
    fid_string.close()
    string_length = sum(tool_lengths)
    return string_length

def get_number_of_tools(string_file):
    """
    Gets the total number of tools in a string.  Tools for which quantitiiy can be defined are only counted once.  The top drive is not included

    Parameters:
        string_file :   Full path to an Adams Drill string file
                      
    Returns:
        num         :   Number of tools  
    """
    with open(string_file, 'r') as fid:
        for line in fid:
            if line.lower().startswith(' stack_order '):
                num = int(line.replace(' ','').split('=')[-1])
    
    return num

def get_bha_length(string_file):
    """
    Gets the total length of the drill string defined in string_file NOT including the equivalent upper string and highest most physical string
    
    Parameters:
        string_file     :   Full path to an Adams Drill string
                            file

    Returns:
        string_length   :   Cumulative length of the string    
    """
    cdbs = get_adrill_cdbs(os.environ['ADRILL_USER_CFG'], os.environ['ADRILL_SHARED_CFG'])
    # print(cdbs)
    tool_lengths = []
    with open(string_file, 'r') as fid_string:
        for line in fid_string:
            if ' property_file' in line.lower() and not line.startswith('$'):
                tool_file = line.split("'")[1].replace('/', '\\')
                if '<' in tool_file:
                    # Get cdb associated with tool_file
                    tool_has_cdb = False
                    for cdb_name in cdbs:
                        if '<{}>'.format(cdb_name) in tool_file:
                            tool_cdb_name = cdb_name
                            cdb_loc = cdbs[cdb_name].replace('/','\\')
                            tool_has_cdb = True
                            break  
                    # Change cdb notation to full path notation
                    if tool_has_cdb:
                        tool_file = tool_file.replace('<{}>'.format(tool_cdb_name), cdb_loc)
                    else:
                        raise cdbError('ADrill Database {} not defined.'.format(cdb_name))
                fid_tool = open(tool_file, 'r')
                file_type = ''
                for line in fid_tool:
                    if file_type and 'top_drive' not in file_type.lower() and line.replace(' ', '').split('=')[0] in TO_LENGTH_PARAM[file_type] and not line.startswith('$'):
                        tool_length = float(line.replace(' ', '').split('=')[1])
                        tool_lengths.append(tool_length)
                        break
                    elif ' file_type' in line.lower() and not line.startswith('$'):
                        file_type = line.lower().replace(' ', '').replace("'",'').replace('\n','').split('=')[1]
                fid_tool.close()
            if ' number_of_joints' in line.lower():
                n = int(line.replace(' ','').split('=')[1])
                tool_lengths[-1] = tool_lengths[-1]*n
    string_length = sum(tool_lengths[:-2])
    return string_length
    
def add_cdb_to_cfg(name, loc, cfg_file):
    """Adds cdb of name 'name' and path 'loc' to cfg_file
    
    Arguments:
        name      {string} -- name of cdb (e.g. example_database)
        loc       {string} -- path to cdb (e.g. C:\\example_database.cdb)
        cfg_file  {string} -- Full filename of an adams drill 
                              configuration file
    
    Raises:
        ValueError      --  Raised if a cdb of the given name or
                            path already exists in the given 
                            config file
        PermissionError --  Raised if the user does not have
                            permissiosn to edit the given config
                            file
    """

    loc = os.path.normpath(loc)
    cdbs = {}

    # Read config file
    with open(cfg_file, 'r') as fid:
        lines = fid.readlines()

    # Pull cdbs from config file into a dictionary
    for line in lines:
        if line.lower().startswith('database'):
            splt = re.split('[ \t]+', line.replace('\n',''), maxsplit=2)
            cdbs[splt[1]] = os.path.normpath(splt[2])
    
    # Check if cdb name already exists
    if name in cdbs:
        raise ValueError('{} already exists in {}.'.format(name, cfg_file))
    
    # Check if cdb location already exists
    for cdb_name in cdbs:
        if loc is cdbs[cdb_name]:
            raise ValueError('{} already exists in {}'.format(loc, cfg_file))
    
    # Add new cdb to cdbs dictionary
    cdbs[name] = loc

    # Rewrite config file with new cdb
    try:
        with open(cfg_file, 'w') as fid:
            cdbs_written = False
            for line in lines:
                if not line.lower().startswith('database'):
                    fid.write(line)
                elif not cdbs_written:
                    for cdb_name, cdb_loc in cdbs.items():
                        text = 'DATABASE   {}   {}\n'.format(cdb_name, cdb_loc)
                        fid.write(text)
                    cdbs_written = True
    except PermissionError:
        raise PermissionError('You do not have permission to edit {}.'.format(cfg_file))    

def remove_cdb_from_cfg(name, cfg_file):
    """Removes cdb of name 'name' from cfg_file
    
    Arguments:
        name {string}       --  Name of cdb (e.g. 
                                example_database)
        cfg_file {string}   --  Full filename of an adams drill
                                configuration file
    
    Raises:
        ValueError      --  Raised if a cdb of the given name or
                            path already exists in the given 
                            config file
        PermissionError --  Raised if the user does not have
                            permissiosn to edit the given config
                            file
    """

    # Initialize cdbs dictionary
    cdbs = {}

    # Read config file
    with open(cfg_file, 'r') as fid:
        lines = fid.readlines()

    # Pull cdbs from config file into a dictionary
    for line in lines:
        if line.lower().startswith('database'):
            splt = re.split('[ \t]+', line.replace('\n',''), maxsplit=2)
            cdbs[splt[1]] = os.path.normpath(splt[2])
    
    # Check if cdb name exists
    if name not in cdbs:
        raise ValueError('{} does not exist in {}.'.format(name, cfg_file))
    
    # Remove cdb from cdbs dictionary
    del cdbs[name]

    # Rewrite config file with the cdb removed
    try:
        with open(cfg_file, 'w') as fid:
            cdbs_written = False
            for line in lines:
                if not line.lower().startswith('database'):
                    fid.write(line)
                elif not cdbs_written:
                    for cdb_name, cdb_loc in cdbs.items():
                        text = 'DATABASE   {}   {}\n'.format(cdb_name, cdb_loc)
                        fid.write(text)
                    cdbs_written = True
    except PermissionError:
        raise PermissionError('You do not have permission to edit {}.'.format(cfg_file))  

def create_cfg_file(filename, database_paths):
    """Create a cfg file with the databases whose paths are given
    in the database_paths list. Also sets the ADRILL_USER_CONFIG
    environment variable equal to filename.
    
    Arguments:
        filename        {string}    -- Filename for the new configuration file.
        database_paths  {list}      -- List of database paths to include in the configuration file. 
    """


    # Create a databases dictionary
    databases = []    
    for path in database_paths:
        name = os.path.split(path)[1].replace('.cdb','')
        databases.append({'name': name, 'path': path})

    # Get the cfg template
    cfg_template = env.get_template('template.cfg')
    
    # Write the new cfg file
    with open(filename ,'w') as fid:
        fid.write(cfg_template.render(databases=databases))
    
    os.environ['ADRILL_USER_CFG'] = os.path.join(os.getcwd(), filename)

def build(string_file, solver_settings_file, working_directory, output_name=None):    
    """Builds adm, acf, and cmd files from string, event, and solver settings files.
    
    Arguments:
        string            {str}     --  Filename of a drill
                                        string (.str) file.
        solver_settings   {str}     --  Filename of a solver 
                                        settings (.ssf) file.
        working_directory {string}  --  Path to the directory to
                                        put the adm, acf, and cmd
    
    Keyword Arguments:
        output_name     {string}    --  Base name of the adm,
                                        acf, and cmd files. 
                                        (default: Same as string_file)
    """    
    # Set the output name  
    if output_name is None:  
        output_name = get_TO_param(string_file, 'OutputName')       
    
    
    # Set the names of the output files
    adm_file = output_name + '.adm'
    
    acf_file = output_name + '.acf'
    cmd_file = output_name + '.cmd'
    
    # Format the string filename    
    adams_formatted_str_filename = os.path.normpath(get_full_path(string_file)).replace(os.sep, '/')
    
    # Set the event filename and solver settings file name (relative paths if in the same directory)    
    event_file = os.path.normpath(get_TO_param(string_file, 'Event_Property_File'))
    if os.path.split(event_file)[0] == os.path.normpath(working_directory):
        evt_name = os.path.split(event_file)[-1]
    else:
        evt_name = os.path.normpath(event_file)
    
    if os.path.split(solver_settings_file)[0] == os.path.normpath(working_directory):
        ssf_name = os.path.split(solver_settings_file)[-1]
    else:
        ssf_name = os.path.split(solver_settings_file)

    # Create aview script
    cmds = []    
    cmds.append(f'ds TOStart string_cfg_file = "{adams_formatted_str_filename}"\n')
    cmds.append(f'adrill build acf ssf="{ssf_name}" evt="{evt_name}"\n')
    cmds.append(f'file adams write file="{adm_file}"\n')
    cmds.append(f'simulation script write_acf sim_script_name = "{output_name}" file_name = "{acf_file}"\n')
    cmds.append(f'file command write entity_name = "{output_name}" file_name = "{cmd_file}"')
    with open(os.path.join(working_directory, 'build.cmd'), 'w') as fid:			
        for cmd in cmds:
            fid.write(cmd)
                                            
    # Run adams to generate adm, acf, cmd
    startupinfo = subprocess.STARTUPINFO()
    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
    process = subprocess.Popen('{} aview ru-s b build.cmd'.format(os.environ['ADAMS_LAUNCH_COMMAND']), cwd=working_directory, startupinfo=startupinfo)
    process.wait()    

    return adm_file, acf_file, cmd_file        

class cdbError(Exception):
    pass
