"""
Copyright 2022 - David Minton, Carlisle Wishard, Jennifer Pouplin, Jake Elliott, & Dana Singh
This file is part of Swiftest.
Swiftest 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 3 of the License, or (at your option) any later version.
Swiftest 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 Swiftest. 
If not, see: https://www.gnu.org/licenses. 
"""

import swiftest
import numpy as np
from scipy.io import FortranFile
import xarray as xr
import sys
import tempfile
import re
import os

# This defines features that are new in Swiftest and not in Swifter (for conversion between param.in files)
newfeaturelist = ("RESTART",
                  "FRAGMENTATION",
                  "ROTATION",
                  "TIDES",
                  "ENERGY",
                  "GR",
                  "YARKOVSKY",
                  "YORP",
                  "IN_FORM",
                  "NC_IN",
                  "SEED",
                  "INTERACTION_LOOPS",
                  "ENCOUNTER_CHECK",
                  "ENCOUNTER_CHECK_PLPL",
                  "ENCOUNTER_CHECK_PLTP",
                  "TSTART",
                  "DUMP_CADENCE",
                  "ENCOUNTER_SAVE",
                  "MIN_GMFRAG",
                  "NFRAG_REDUCTION",
                  "COLLISION_MODEL",
                  "COARRAY")

# This list defines features that are booleans, so must be converted to/from string when writing/reading from file
bool_param = ["RESTART",
              "CHK_CLOSE",
              "EXTRA_FORCE",
              "RHILL_PRESENT",
              "BIG_DISCARD",
              "FRAGMENTATION",
              "ROTATION",
              "TIDES",
              "ENERGY",
              "GR",
              "YARKOVSKY",
              "YORP",
              "COARRAY"]

int_param = ["ISTEP_OUT", "DUMP_CADENCE"]
float_param = ["T0", "TSTART", "TSTOP", "DT", "CHK_RMIN", "CHK_RMAX", "CHK_EJECT", "CHK_QMIN", "DU2M", "MU2KG",
               "TU2S", "MIN_GMFRAG", "GMTINY"]

upper_str_param = ["OUT_TYPE","OUT_FORM","OUT_STAT","IN_TYPE","IN_FORM","ENCOUNTER_SAVE", "CHK_QMIN_COORD"]
lower_str_param = ["NC_IN", "PL_IN", "TP_IN", "CB_IN", "CHK_QMIN_RANGE"]

param_keys = ['! VERSION'] + int_param + float_param + upper_str_param + lower_str_param+ bool_param

# This defines Xarray Dataset variables that are strings, which must be processed due to quirks in how NetCDF-Fortran
# handles strings differently than Python's Xarray.
string_varnames = ["name", "particle_type", "origin_type", "stage", "regime"]
char_varnames = ["space"]
int_varnames = ["id", "ntp", "npl", "nplm", "discard_body_id", "collision_id", "status"]

def _bool2yesno(boolval):
    """
    Converts a boolean into a string of either "YES" or "NO".

    Parameters
    ----------
    boolval : bool
        Input value

    Returns
    -------
    str 
        {"YES","NO"}
    """
    if boolval:
        return "YES"
    else:
        return "NO"
 
 
def _bool2tf(boolval):
    """
    Converts a boolean into a string of either "T" or "F".

    Parameters
    ----------
    boolval : bool
        Input value

    Returns
    -------
    str
        {"T","F"}

    """
    if boolval:
        return "T"
    else:
        return "F"


def _str2bool(input_str):
    """
    Converts a string into an equivalent boolean.

    Parameters
    ----------
    input_str : {"YES", "Y", "T", "TRUE", ".TRUE.", "NO", "N", "F", "FALSE", ".FALSE."}
        Input string. Input is case-insensitive.

    Returns
    -------
    bool 

    """
    if type(input_str) is bool:
       return input_str
    valid_true = ["YES", "Y", "T", "TRUE", ".TRUE."]
    valid_false = ["NO", "N", "F", "FALSE", ".FALSE."]
    if input_str.upper() in valid_true:
        return True
    elif input_str.upper() in valid_false:
        return False
    else:
        raise ValueError(f"{input_str} is not recognized as boolean")


def _real2float(realstr):
    """
    Converts a Fortran-generated ASCII string of a real value into a numpy float type. Handles cases where double precision
    numbers in exponential notation use 'd' or 'D' instead of 'e' or 'E'
    
    Parameters
    ----------
    realstr : str
        Fortran-generated ASCII string of a real value.

    Returns
    -------
    float
        The converted floating point value of the input string
    """
    return float(realstr.replace('d', 'E').replace('D', 'E'))


def read_swiftest_param(param_file_name, param, verbose=True):
    """
    Reads in a Swiftest param.in file and saves it as a dictionary

    Parameters
    ----------
    param_file_name : string
        File name of the input parameter file

    Returns
    -------
    param : dict
        A dictionary containing the entries in the user parameter file
    """
    param['! VERSION'] = f"Swiftest parameter input file"

    # Read param.in file
    if verbose: print(f'Reading Swiftest file {param_file_name}')
    try:
        with open(param_file_name, 'r') as f:
            for line in f.readlines():
                fields = line.split()
                if len(fields) > 0:
                    if fields[0][0] != '!':
                        key = fields[0].upper()
                        param[key] = fields[1]
                    # Special case of CHK_QMIN_RANGE requires a second input
                    if fields[0].upper() == 'CHK_QMIN_RANGE':
                        alo = _real2float(fields[1])
                        ahi = _real2float(fields[2])
                        param['CHK_QMIN_RANGE'] = f"{alo} {ahi}"

        for uc in upper_str_param:
            if uc in param:
                param[uc] = param[uc].upper()

        for i in int_param:
            if i in param and type(param[i]) != int:
                param[i] = int(float(param[i]))

        for f in float_param:
            if f in param and type(param[f]) is str:
                param[f] = _real2float(param[f])

        for b in bool_param:
            if b in param:
                param[b] = _str2bool(param[b])
    except IOError:
        print(f"{param_file_name} not found.")
    return param


def reorder_dims(ds):
    """
    Re-order dimension coordinates so that they are in the same order as the Fortran side
    
    Parameters
    ----------
    ds : xarray dataset
    
    Returns
    -------
    ds : xarray dataset with the dimensions re-ordered
    """
    idx = ds.indexes
    if "id" in idx:
        dim_order = ["time", "id", "space"]
    elif "name" in idx:
        dim_order = ["time", "name", "space"]
    else:
        dim_order = idx
    idx = {index_name: idx[index_name] for index_name in dim_order}
    ds = ds.reindex(idx)
    return ds


def read_swifter_param(param_file_name, verbose=True):
    """
    Reads in a Swifter param.in file and saves it as a dictionary

    Parameters
    ----------
    param_file_name : string
        File name of the input parameter file
    verbose : bool, default True
        Print out information about the file being read
    Returns
    -------
    param : dict
        A dictionary containing the entries in the user parameter file
    """
    param = {
        '! VERSION': f"Swifter parameter input from file {param_file_name}",
        'T0': "0.0",
        'TSTOP': "0.0",
        'DT': "0.0",
        'PL_IN': "",
        'TP_IN': "",
        'IN_TYPE': "ASCII",
        'ISTEP_OUT': "-1",
        'ISTEP_DUMP': "-1",
        'BIN_OUT': "bin.dat",
        'OUT_TYPE': "REAL8",
        'OUT_FORM': "XV",
        'OUT_STAT': "NEW",
        'J2': "0.0",
        'J4': "0.0",
        'CHK_CLOSE': False,
        'CHK_RMIN': "-1.0",
        'CHK_RMAX': "-1.0",
        'CHK_EJECT': "-1.0",
        'CHK_QMIN': "-1.0",
        'CHK_QMIN_COORD': "HELIO",
        'CHK_QMIN_RANGE': "",
        'ENC_OUT': "",
        'EXTRA_FORCE': False,
        'BIG_DISCARD': False,
        'RHILL_PRESENT': False,
        'C': "-1.0",
    }
    
    # Read param.in file
    if verbose: print(f'Reading Swifter file {param_file_name}')
    try:
        with open(param_file_name, 'r') as f:
            for line in f.readlines():
                fields = line.split()
                if len(fields) > 0:
                    for key in param:
                        if (key == fields[0].upper()): param[key] = fields[1]
                    # Special case of CHK_QMIN_RANGE requires a second input
                    if fields[0].upper() == 'CHK_QMIN_RANGE':
                        alo = _real2float(fields[1])
                        ahi = _real2float(fields[2])
                        param['CHK_QMIN_RANGE'] = f"{alo} {ahi}"
    
        param['ISTEP_OUT'] = int(param['ISTEP_OUT'])
        param['ISTEP_DUMP'] = int(param['ISTEP_DUMP'])
        param['T0'] = _real2float(param['T0'])
        param['TSTOP'] = _real2float(param['TSTOP'])
        param['DT'] = _real2float(param['DT'])
        param['J2'] = _real2float(param['J2'])
        param['J4'] = _real2float(param['J4'])
        param['CHK_RMIN'] = _real2float(param['CHK_RMIN'])
        param['CHK_RMAX'] = _real2float(param['CHK_RMAX'])
        param['CHK_EJECT'] = _real2float(param['CHK_EJECT'])
        param['CHK_QMIN'] = _real2float(param['CHK_QMIN'])
        for b in bool_param:
            if b in param:
                param[b] = _str2bool(param[b])
        if param['C'] != '-1.0':
            param['C'] = _real2float(param['C'])
        else:
            param.pop('C', None)
    except IOError:
        print(f"{param_file_name} not found.")

    return param


def read_swift_param(param_file_name, startfile="swift.in", verbose=True):
    """
    Reads in a Swift param.in file and saves it as a dictionary

    Parameters
    ----------
    param_file_name : string
        File name of the input parameter file
    startfile : string, default "swift.in"
        File name of the input start file
        
    Returns
    -------
    param : dict
        A dictionary containing the entries in the user parameter file
    """
    param = {
        '! VERSION': f"Swift parameter input from file {param_file_name}",
        'T0': 0.0,
        'TSTOP': 0.0,
        'DT': 0.0,
        'DTOUT': 0.0,
        'DTDUMP': 0.0,
        'L1': "F",
        'L2': "F",
        'L3': "F",
        'L4': "F",
        'L5': "F",
        'L6': "F",
        'RMIN': -1,
        'RMAX': -1,
        'RMAXU': -1,
        'QMIN': -1,
        'LCLOSE': "F",
        'BINARY_OUTPUTFILE': "bin.dat",
        'STATUS_FLAG_FOR_OPEN_STATEMENTS': "NEW",
    }
    
    try:
        with open(startfile, 'r') as f:
            line = f.readline()
            plname = f.readline().split()[0]
            tpname = f.readline().split()[0]
    except:
        plname = "pl.in"
        tpname = "tp.in"
    param['PL_IN'] = plname
    param['TP_IN'] = tpname

    # Read param.in file
    if verbose: print(f'Reading Swift file {param_file_name}')
    try:
        with open(param_file_name, 'r') as f:
            line = f.readline().split()
            for i, l in enumerate(line):
                line[i] = l
            param['T0'] = _real2float(line[0])
            param['TSTOP'] = _real2float(line[1])
            param['DT'] = _real2float(line[2])
            line = f.readline().split()
            for i, l in enumerate(line):
                line[i] = l
            param['DTOUT'] = _real2float(line[0])
            param['DTDUMP'] = _real2float(line[1])
            line = f.readline().split()
            param['L1'] = line[0].upper()
            param['L2'] = line[1].upper()
            param['L3'] = line[2].upper()
            param['L4'] = line[3].upper()
            param['L5'] = line[4].upper()
            param['L6'] = line[5].upper()
            if param['L2'] == "T":
                line = f.readline().split()
                for i, l in enumerate(line):
                    line[i] = l
                param['RMIN'] = _real2float(line[0])
                param['RMAX'] = _real2float(line[1])
                param['RMAXU'] = _real2float(line[2])
                param['QMIN'] = _real2float(line[3])
                param['LCLOSE'] = line[4].upper()
            param['BINARY_OUTPUTFILE'] = f.readline().strip()
            param['STATUS_FLAG_FOR_OPEN_STATEMENTS'] = f.readline().strip().upper()
    except IOError:
       print(f"{param_file_name} not found.")
    
    return param


def write_swift_param(param, param_file_name):
    """
    Writes a Swift param.in file.

    Parameters
    ----------
    param : dictionary
        The entries in the user parameter file
    param_file_name : string
        File name of the input parameter file

    Returns
    -------
    None
    """
    outfile = open(param_file_name, 'w')
    print(param['T0'], param['TSTOP'], param['DT'], file=outfile)
    print(param['DTOUT'], param['DTDUMP'], file=outfile)
    print(param['L1'], param['L2'], param['L3'], param['L4'], param['L5'], param['L6'], file=outfile)
    print(param['RMIN'], param['RMAX'], param['RMAXU'], param['QMIN'], param['LCLOSE'], file=outfile)
    print(param['BINARY_OUTPUTFILE'], file=outfile)
    print(param['STATUS_FLAG_FOR_OPEN_STATEMENTS'], file=outfile)
    outfile.close()
    return


def write_labeled_param(param, param_file_name):
    """
    Writes a Swifter/Swiftest param.in file.

    Parameters
    ----------
    param : dictionary
        The entries in the user parameter file
    param_file_name : string
        File name of the input parameter file

    Returns
    -------
    None
    """
    outfile = open(param_file_name, 'w')
    ptmp = param.copy()
    # Print the list of key/value pairs in the preferred order
    for key in param_keys:
        val = ptmp.pop(key, None)
        if val is not None:
            if type(val) is bool:
                print(f"{key:<32} {_bool2yesno(val)}", file=outfile)
            else:
                print(f"{key:<32} {val}", file=outfile)
    # Print the remaining key/value pairs in whatever order
    for key, val in ptmp.items():
        if val != "":
            if type(val) is bool:
                print(f"{key:<32} {_bool2yesno(val)}", file=outfile)
            else:
                print(f"{key:<32} {val}", file=outfile)
    outfile.close()
    return


def _swifter_stream(f, param):
    """
    Reads in a Swifter bin.dat file and returns a single frame of data as a datastream

    Parameters
    ----------
    f : file object
        File object of the Swifter bin.dat file
    param : dict
        Swifter parameters

    Yields
    -------
    t    : float
        Time of this frame
    npl  : int
        Number of massive bodies
    plid : int array
        IDs of massive bodies
    pvec : float array
        (npl,N) - vector of N quantities or each particle (6 of XV/EL + Gmass, radius, etc)
    plab : string list
        Labels for the pvec data
    ntp  : int
        Number of test particles
    tpid : int array
        Ids of test particles
    tvec : float array
        (ntp,N) - vector of N quantities for each particle (6 of XV/EL, etc.)
    tlab : string list
        Labels for the tvec data
    """
    
    while True:  # Loop until you read the end of file
        try:
            # Read single-line header
            record = f.read_record('<f8', '<i4', '<i4', '<i4')
        except:
            break
        t = record[0]
        npl = record[1][0] - 1
        ntp = record[2][0]
       
        if param['OUT_FORM'] == 'XV':
            rpvec = np.empty((npl,3))
            vpvec = np.empty((npl,3))
            rtvec = np.empty((ntp,3))
            vtvec = np.empty((ntp,3))
        elif param['OUT_FORM'] == 'EL': 
            elempvec = np.empty((npl,6))
            elemtvec = np.empty((ntp,6))
        plid = np.empty(npl, dtype='int')
        tpid = np.empty(ntp, dtype='int')
        if npl > 0:
            GMpl = np.empty(npl)
            Rpl = np.empty(npl)
            for i in range(npl):
                # Read single-line pl frame for
                if param['OUT_FORM'] == 'XV':
                    record = f.read_record('<i4', '<f8', '<f8', '(3,)<f8', '(3,)<f8')
                elif param['OUT_FORM'] == 'EL':
                    record = f.read_record('<i4', '<f8', '<f8', '(6,)<f8')
                plid[i] = record[0]
                GMpl[i] = record[1]
                Rpl[i] = record[2]
                if param['OUT_FORM'] == 'XV':
                    rpvec[i,:] = record[3]
                    vpvec[i,:] = record[4]
                elif param['OUT_FORM'] == 'EL':
                    elempvec[i,:] = record[3]
        if ntp > 0:
            for i in range(ntp):
                if param['OUT_FORM'] == 'XV':
                    record = f.read_record('<i4', '(3,)<f8', '(3,)<f8')
                elif param['OUT_FORM'] == 'EL':
                    record = f.read_record('<i4', '(6,)<f8')
                tpid[i] = record[0]
                if param['OUT_FORM'] == 'XV':
                    rtvec[i,:] = record[1]
                    vtvec[i,:] = record[2]
                elif param['OUT_FORM'] == 'EL':
                    elemtvec[i,:] = record[1]
        
        tlab = ['id']
        if param['OUT_FORM'] == 'XV': 
            tlab.append('rh')
            tlab.append('vh')
        elif param['OUT_FORM'] == 'EL':
            tlab.append('a')
            tlab.append('e')
            tlab.append('inc')
            tlab.append('capom')
            tlab.append('omega')
            tlab.append('capm')
        plab = tlab.copy()
        plab.append('Gmass')
        plab.append('radius')
        
        if param['OUT_FORM'] == 'XV':
            yield t, npl, [plid, rpvec, vpvec, GMpl, Rpl], plab, \
                  ntp, [tpid, rtvec, vtvec], tlab
        elif param['OUT_FORM'] == 'EL':
            yield t, npl, [plid, elempvec, GMpl, Rpl], plab, \
                  ntp, [tpid, elemtvec], tlab


def swifter2xr(param, verbose=True):
    """
    Converts a Swifter binary data file into an xarray DataSet.

    Parameters
    ----------
    param : dict
        Swifter parameters
    verbose : bool, default True
        Print out information about the file being read
    Returns
    -------
    xarray dataset
    """
    dims = ['time', 'id','vec']
    pl = []
    tp = []
    with FortranFile(param['BIN_OUT'], 'r') as f:
        for t, npl, pvec, plab, \
            ntp, tvec, tlab in _swifter_stream(f, param):
            
            sys.stdout.write('\r' + f"Reading in time {t[0]:.3e}")
            sys.stdout.flush()
            
            pvec_args = dict(zip(plab,pvec))
            pl.append(swiftest.init_cond.vec2xr(param,time=t,**pvec_args))
            if ntp > 0:
                tvec_args = dict(zip(tlab,tvec))
                tp.append(swiftest.init_cond.vec2xr(param,time=t,**tvec_args))
            
        plds = xr.concat(pl, dim='time')
        if len(tp) > 0: 
            tpds = xr.concat(tp, dim='time')
        
        if verbose: print('\nCreating Dataset')
        if len(tp) > 0:
            ds = xr.combine_by_coords([plds, tpds])
        if verbose: print(f"Successfully converted {ds.sizes['time']} output frames.")
    return ds


def process_netcdf_input(ds, param):
    """
    Performs several tasks to convert raw NetCDF files output by the Fortran program into a form that
    is used by the Python side. These include:
    - Ensuring all types are correct

    Parameters
    ----------
    ds : Xarray dataset

    Returns
    -------
    ds : xarray dataset
    """

    if param['OUT_TYPE'] == "NETCDF_DOUBLE":
        ds = fix_types(ds,ftype=np.float64)
    elif param['OUT_TYPE'] == "NETCDF_FLOAT":
        ds = fix_types(ds,ftype=np.float32)

    return ds


def swiftest2xr(param, verbose=True, dask=False):
    """
    Converts a Swiftest binary data file into an xarray DataSet.

    Parameters
    ----------
    param : dict
        Swiftest parameters
    verbose : bool, default True
        Print out information about the file being read
    dask : bool, default False
        Use Dask to lazily load data (useful for very large datasets)

    Returns
    -------
    xarray dataset
    """


    if ((param['OUT_TYPE'] == 'NETCDF_DOUBLE') or (param['OUT_TYPE'] == 'NETCDF_FLOAT')):
        if verbose: print('\nCreating Dataset from NetCDF file')
        if dask:
            ds = xr.open_mfdataset(param['BIN_OUT'], engine='h5netcdf', mask_and_scale=False)
        else:
            ds = xr.open_dataset(param['BIN_OUT'], mask_and_scale=False)
        
        ds = process_netcdf_input(ds, param)
    else:
        print(f"Error encountered. OUT_TYPE {param['OUT_TYPE']} not recognized.")
        return None
    if verbose: print(f"Successfully converted {ds.sizes['time']} output frames.")

    return ds


def _xstrip_nonstr(a):
    """
    Cleans up the string values in the DataSet to remove extra white space

    Parameters
    ----------
    a  : xarray
        Input dataset

    Returns
    -------
    da : xarray dataset with the strings cleaned up
    """
    func = lambda x: np.char.strip(x)
    return xr.apply_ufunc(func, a.str.decode(encoding='utf-8'),dask='parallelized')


def _xstrip_str(a):
    """
    Cleans up the string values in the DataSet to remove extra white space

    Parameters
    ----------
    a    : xarray dataset

    Returns
    -------
    da : xarray dataset with the strings cleaned up
    """
    func = lambda x: np.char.strip(x)
    return xr.apply_ufunc(func, a,dask='parallelized')


def _string_converter(da):
    """
    Converts a string to a unicode string 

    Parameters
    ----------
    da    : xarray dataset

    Returns
    -------
    da : xarray dataset with the strings cleaned up
    """

    da = da.astype('<U32')
    da = _xstrip_str(da)

    return da


def _char_converter(da):
    """`
    Converts a string to a unicode string

    Parameters
    ----------
    da    : xarray dataset

    Returns
    -------
    da : xarray dataset with the strings cleaned up
    """
    if da.dtype == np.dtype(object):
        da = da.astype('<U1')
    elif type(da.values[0]) != np.str_:
        da = _xstrip_nonstr(da)
    return da


def _clean_string_values(ds):
    """
    Cleans up the string values in the DataSet that have artifacts as a result of coming from NetCDF Fortran

    Parameters
    ----------
    ds    : xarray dataset
 
    Returns
    -------
    ds : xarray dataset with the strings cleaned up
    """

    for n in string_varnames:
        if n in ds:
           ds[n] = _string_converter(ds[n])

    for n in char_varnames:
        if n in ds:
            ds[n] = _char_converter(ds[n])
    return ds


def _unclean_string_values(ds):
    """
    Returns strings back to a format readable to NetCDF Fortran

    Parameters
    ----------
    ds    : xarray dataset

    Returns
    -------
    ds : xarray dataset with the strings cleaned up
    """

    for c in string_varnames:
        if c in ds:
            n = _string_converter(ds[c])
            ds[c] = n.str.ljust(32).str.encode('utf-8')

    for c in char_varnames:
        if c in ds:
            n = _string_converter(ds[c])
            ds[c] = n.str.ljust(1).str.encode('utf-8')
    return ds


def fix_types(ds,itype=np.int64,ftype=np.float64):
    """
    Converts all variables in the dataset to the specified type
    
    Parameters
    ----------
    ds    : xarray dataset
        Input dataset to convert
    itype : numpy type, default np.int64
        Integer type to convert to
    ftype : numpy type, default np.float64
        Floating point type to convert to 
        
    Returns
    -------
    ds : xarray dataset with the types converted
    """
    ds = _clean_string_values(ds)
    for intvar in int_varnames:
        if intvar in ds:
            ds[intvar] = ds[intvar].astype(itype)

    float_varnames = [x for x in list(ds.keys()) if x not in string_varnames + int_varnames + char_varnames]

    for floatvar in float_varnames:
        ds[floatvar] = ds[floatvar].astype(ftype)

    float_coordnames = [x for x in list(ds.coords) if x not in string_varnames + int_varnames + char_varnames]
    for floatcoord in float_coordnames:
        ds[floatcoord] = ds[floatcoord].astype(np.float64)


    return ds


def select_active_from_frame(ds, param, framenum=-1):
    """
    Selects a particular frame from a DataSet and returns only the active particles in that frame

    Parameters
    ----------
    ds : xarray dataset
        Dataset containing Swiftest n-body data
    param : dict
        Swiftest input parameters. This method uses the names of the cb, pl, and tp files from the input
    framenum : int, default=-1
        Time frame to extract. If this argument is not passed, the default is to use the last frame in the dataset.

    Returns
    -------
    xarray dataset
        Dataset containing only the active particles at frame, framenum
    """


    # Select the input time frame from the Dataset
    frame = ds.isel(time=[framenum])
    iframe = frame.isel(time=0)

    if "name" in ds.dims:
        count_dim = "name"
    elif "id" in ds.dims:
        count_dim = "id"

    # Select only the active particles at this time step
    # Remove the inactive particles
    if param['OUT_FORM'] == 'XV' or param['OUT_FORM'] == 'XVEL':
        if 'rh' in iframe:
            iactive = iframe[count_dim].where((~np.isnan(iframe['Gmass'])) | (~np.isnan(iframe['rh'].isel(space=0))), drop=True)[count_dim]
        else:
            iactive = iframe[count_dim].where(~np.isnan(iframe['Gmass']))
    else:
        if 'a' in iframe:    
            iactive = iframe[count_dim].where((~np.isnan(iframe['Gmass'])) | (~np.isnan(iframe['a'])), drop = True)[count_dim]
        else:
            iactive = iframe[count_dim].where(~np.isnan(iframe['Gmass']))
    if count_dim == "id":
        frame = frame.sel(id=iactive.values)
    elif count_dim == "name":
        frame = frame.sel(name=iactive.values)


    return frame


def swiftest_xr2infile(ds, param, in_type="NETCDF_DOUBLE", infile_name=None,framenum=-1,verbose=True):
    """
    Writes a set of Swiftest input files from a single frame of a Swiftest xarray dataset

    Parameters
    ----------
    ds : xarray dataset
        Dataset containing Swiftest n-body data in XV format
    param : dict
        Swiftest input parameters. This method uses the names of the cb, pl, and tp files from the input
    framenum : int (default=-1)
        Time frame to use to generate the initial conditions. If this argument is not passed, the default is to use the last frame in the dataset.

    Returns
    -------
    None
    """
    param_tmp = param.copy()
    param_tmp['OUT_FORM'] = param['IN_FORM']
    frame = select_active_from_frame(ds, param_tmp, framenum)


    if in_type == "NETCDF_DOUBLE" or in_type == "NETCDF_FLOAT":
        # Convert strings back to byte form and save the NetCDF file
        # Note: xarray will call the character array dimension string32. The Fortran code
        # will rename this after reading

        if infile_name is None:
            infile_name = param['NC_IN']
        frame = _unclean_string_values(frame)
        if verbose:
            print(f"Writing initial conditions to file {infile_name}")
        frame = reorder_dims(frame)
        frame.to_netcdf(path=infile_name)
        return frame

    # All other file types need seperate files for each of the inputs
    cb = frame.isel(name=0)
    pl = frame.where(name != cb.name)
    pl = pl.where(np.invert(np.isnan(pl['Gmass'])), drop=True).drop_vars(['j2rp2', 'j2rp2'],errors="ignore")
    tp = frame.where(np.isnan(frame['Gmass']), drop=True).drop_vars(['Gmass', 'radius', 'j2rp2', 'j4rp4'],errors="ignore")
    
    GMSun = np.double(cb['Gmass'])
    if param['CHK_CLOSE']:
       RSun = np.double(cb['radius'])
    else:
       RSun = param['CHK_RMIN']
    J2 = np.double(cb['j2rp2'])
    J4 = np.double(cb['j4rp4'])
    cbname = cb['name'].values[0]
    if param['ROTATION']:
        Ip1cb = np.double(cb['Ip'].values[0])
        Ip2cb = np.double(cb['Ip'].values[1])
        Ip3cb = np.double(cb['Ip'].values[2])
        rotxcb = np.double(cb['rot'].values[0])
        rotycb = np.double(cb['rot'].values[1])
        rotzcb = np.double(cb['rot'].values[2])
    cbid = int(0)
    
    if in_type == 'ASCII':
        # Swiftest Central body file
        cbfile = open(param['CB_IN'], 'w')
        print(cbname, file=cbfile)
        print(GMSun, file=cbfile)
        print(RSun, file=cbfile)
        print(J2, file=cbfile)
        print(J4, file=cbfile)
        if param['ROTATION']:
            print(Ip1cb, Ip2cb, Ip3cb, file=cbfile)
            print(rotxcb, rotycb, rotzcb, file=cbfile)
        cbfile.close()
        
        plfile = open(param['PL_IN'], 'w')
        print(pl.id.count().values, file=plfile)
        for i in pl.id:
            pli = pl.sel(id=i)
            if param['RHILL_PRESENT']:
               print(pli['name'].values[0], pli['Gmass'].values[0], pli['rhill'].values[0], file=plfile)
            else:
               print(pli['name'].values[0], pli['Gmass'].values[0], file=plfile)
            if param['CHK_CLOSE']:
               print(pli['radius'].values[0], file=plfile)
            if param['IN_FORM'] == 'XV':
                print(pli['rh'].values[0,0], pli['rh'].values[0,1], pli['rh'].values[0,2], file=plfile)
                print(pli['vh'].values[0,0], pli['vh'].values[0,1], pli['vh'].values[0,2], file=plfile)
            elif param['IN_FORM'] == 'EL':
                print(pli['a'].values[0], pli['e'].values[0], pli['inc'].values[0], file=plfile)
                print(pli['capom'].values[0], pli['omega'].values[0], pli['capm'].values[0], file=plfile)
            else:
                print(f"{param['IN_FORM']} is not a valid input format type.")
            if param['ROTATION']:
                print(pli['Ip'].values[0,0], pli['Ip'].values[0,1], pli['Ip'].values[0,2], file=plfile)
                print(pli['rot'].values[0,0], pli['rot'].values[0,1], pli['rot'].values[0,2], file=plfile)
        plfile.close()
        
        # TP file
        tpfile = open(param['TP_IN'], 'w')
        print(tp.id.count().values, file=tpfile)
        for i in tp.id:
            tpi = tp.sel(id=i)
            print(tpi['name'].values[0], file=tpfile)
            if param['IN_FORM'] == 'XV':
                print(tpi['rh'].values[0,0], tpi['rh'].values[0,1], tpi['rh'].values[0,2], file=tpfile)
                print(tpi['vh'].values[0,0], tpi['vh'].values[0,1], tpi['vh'].values[0,2], file=tpfile)
            elif param['IN_FORM'] == 'EL':
                print(tpi['a'].values[0], tpi['e'].values[0], tpi['inc'].values[0], file=tpfile)
                print(tpi['capom'].values[0], tpi['omega'].values[0], tpi['capm'].values[0], file=tpfile)
            else:
                print(f"{param['IN_FORM']} is not a valid input format type.")
        tpfile.close()
    else:
        print(f"{in_type} is an unknown file type")
    return


def swifter_xr2infile(ds, param, simdir=os.getcwd, framenum=-1):
    """
    Writes a set of Swifter input files from a single frame of a Swiftest xarray dataset

    Parameters
    ----------
    ds : xarray dataset
        Dataset containing Swifter n-body data in XV format
    framenum : int
        Time frame to use to generate the initial conditions. If this argument is not passed, the default is to use the last frame in the dataset.
    param : dict
        Swifter input parameters. This method uses the names of the pl and tp files from the input

    Returns
    -------
    A set of input files for a Swifter run
    """

    frame = ds.isel(time=framenum)

    if "name" in frame.dims:
        frame = frame.swap_dims({"name" : "id"})
        frame = frame.reset_coords("name")

    cb = frame.where(frame.id == 0, drop=True)
    pl = frame.where(frame.id > 0, drop=True)
    pl = pl.where(np.invert(np.isnan(pl['Gmass'])), drop=True).drop_vars(['j2rp2', 'j4rp4'])
    tp = frame.where(np.isnan(frame['Gmass']), drop=True).drop_vars(['Gmass', 'radius', 'j2rp2', 'j4rp4'])
    
    GMSun = np.double(cb['Gmass'])
    if param['CHK_CLOSE']:
       RSun = np.double(cb['radius'])
    else:
       RSun = param['CHK_RMIN']
    param['J2'] = np.double(cb['j2rp2'])
    param['J4'] = np.double(cb['j4rp4'])
    
    if param['IN_TYPE'] == 'ASCII':
        # Swiftest Central body file
        plfile = open(os.path.join(simdir,param['PL_IN']), 'w')
        print(pl.id.count().values + 1, file=plfile)
        print(cb.id.values[0], GMSun, file=plfile)
        print('0.0 0.0 0.0', file=plfile)
        print('0.0 0.0 0.0', file=plfile)
        for i in pl.id:
            pli = pl.sel(id=i)
            if param['RHILL_PRESENT']:
                print(i.values, pli['Gmass'].values, pli['rhill'].values, file=plfile)
            else:
                print(i.values, pli['Gmass'].values, file=plfile)
            if param['CHK_CLOSE']:
                print(pli['radius'].values, file=plfile)
            print(pli['rh'].values[0], pli['rh'].values[1], pli['rh'].values[2], file=plfile)
            print(pli['vh'].values[0], pli['vh'].values[1], pli['vh'].values[2], file=plfile)
        plfile.close()
        
        # TP file
        tpfile = open(os.path.join(simdir,param['TP_IN']), 'w')
        print(tp.id.count().values, file=tpfile)
        for i in tp.id:
            tpi = tp.sel(id=i)
            print(i.values, file=tpfile)
            print(tpi['rh'].values[0], tpi['rh'].values[1], tpi['rh'].values[2], file=tpfile)
            print(tpi['vh'].values[0], tpi['vh'].values[1], tpi['vh'].values[2], file=tpfile)
        tpfile.close()
    else:
        print(f"{param['IN_TYPE']} is an unknown input file type")

    return


def swift2swifter(swift_param, plname="", tpname="", conversion_questions={}):
    """
    Converts from a Swift run to a Swifter run

    Parameters
    ----------
    swift_param : dictionary
       Swift input parameters. 
    plname : string
        Name of massive body input file
    tpname : string
        Name of test particle input file
    conversion_questions : dictronary
        Dictionary of additional parameters required to convert between formats

    Returns
    -------
    swifter_param : A set of input files for a new Swifter run
    """

    swifter_param = {}
    intxt = conversion_questions.get('RHILL', None)
    if not intxt:
        intxt = input("Is this a SyMBA input file with RHILL values in pl.in? (y/N)> ")
    if intxt.upper() == 'Y':
        isSyMBA = True
        swifter_param['RHILL_PRESENT'] = True
    else:
        isSyMBA = False
        swifter_param['RHILL_PRESENT'] = False
        
    isDouble = conversion_questions.get('DOUBLE', None)
    if not isDouble:
        print("Use single precision or double precision for real outputs?")
        print(" 1) Single (real*4)")
        print("*2) Double (real*8)")
        intxt = input("> ")
        if intxt == '1':
            isDouble = False
        else:
            isDouble = True
        
    # Convert the parameter file values
    swifter_param['T0'] = swift_param['T0']
    swifter_param['TSTOP'] = swift_param['TSTOP']
    swifter_param['DT'] = swift_param['DT']
    swifter_param['ISTEP_OUT'] = int(swift_param['DTOUT'] / swift_param['DT'])
    swifter_param['ISTEP_DUMP'] = int(swift_param['DTDUMP'] / swift_param['DT'])
    swifter_param['BIN_OUT'] = swift_param['BINARY_OUTPUTFILE']
    swifter_param['OUT_STAT'] = swift_param['STATUS_FLAG_FOR_OPEN_STATEMENTS']
    
    if swift_param['L5'] == "T":
        swifter_param['OUT_FORM'] = 'EL'
    else:
        swifter_param['OUT_FORM'] = 'XV'
    
    if swift_param['LCLOSE'] == "T":
        swifter_param['CHK_CLOSE'] = True
    else:
        swifter_param['CHK_CLOSE'] = False
        
    swifter_param['CHK_RMIN'] = swift_param['RMIN']
    swifter_param['CHK_RMAX'] = swift_param['RMAX']
    swifter_param['CHK_QMIN'] = swift_param['QMIN']
    if swift_param['QMIN'] != '-1':
        
        swifter_param['CHK_QMIN_COORD'] = conversion_questions.get('CHK_QMIN_COORD', None)
        if not swifter_param['CHK_QMIN_COORD']:
            print("CHK_QMIN_COORD value:")
            print("*1) HELIO")
            print(" 2) BARY")
            intxt = input("> ")
            if intxt == '2':
                swifter_param['CHK_QMIN_COORD'] = "BARY"
            else:
                swifter_param['CHK_QMIN_COORD'] = "HELIO"

        swifter_param['CHK_QMIN_RANGE'] = conversion_questions.get('CHK_QMIN_RANGE', None)
        if not swifter_param['CHK_QMIN_RANGE']:
            alo = input(f"Lower bound on CHK_QMIN_RANGE [{swift_param['RMIN']}]: ")
            if alo == '':
                alo = swift_param['RMIN']
            ahi = input(f"Upper bound on CHK_QMIN_RANGE: [{swift_param['RMAXU']}]: ")
            if ahi == '':
                ahi = swift_param['RMAXU']
            swifter_param['CHK_QMIN_RANGE'] = f"{alo} {ahi}"

    
    swifter_param['ENC_OUT'] = conversion_questions.get('ENC_OUT', None)
    if not swifter_param['ENC_OUT']:
        swifter_param['ENC_OUT'] = input("ENC_OUT: Encounter file name: [enc.dat]> ")
        if swifter_param['ENC_OUT'] == '':
            swifter_param['ENC_OUT'] = "enc.dat"

    intxt = conversion_questions.get('EXTRA_FORCE', None)
    if not intxt:
        intxt = input("EXTRA_FORCE: Use additional user-specified force routines? (y/N)> ")
    if intxt.upper() == 'Y':
        swifter_param['EXTRA_FORCE'] = True
    else:
        swifter_param['EXTRA_FORCE'] = False

    intxt = conversion_questions.get('BIG_DISCARD', None)
    if not intxt:
        intxt = input("BIG_DISCARD: include data for all bodies > GMTINY for each discard record? (y/N)> ")
    if intxt.upper() == 'Y':
        swifter_param['BIG_DISCARD'] = True
    else:
        swifter_param['BIG_DISCARD'] = False
    
    # Convert the PL file
    if plname == '':
        plname = input("PL_IN: Name of new planet input file: [pl.swifter.in]> ")
        if plname == '':
            plname = "pl.swifter.in"
    swifter_param['PL_IN'] = plname
    try:
        plnew = open(swifter_param['PL_IN'], 'w')
    except IOError:
        print(f"Cannot write to file {swifter_param['PL_IN']}")
        return swift_param
    
    print(f"Converting PL file: {swift_param['PL_IN']} -> {swifter_param['PL_IN']}")
    try:
        with open(swift_param['PL_IN'], 'r') as plold:
            line = plold.readline()
            i_list = [i for i in re.split('  +|\t',line) if i.strip()]
            npl = int(i_list[0])
            print(npl, file=plnew)
            line = plold.readline()
            i_list = [i for i in re.split('  +|\t',line) if i.strip()]
            GMcb = _real2float(i_list[0])
            if swift_param['L1'] == "T":
                swifter_param['J2'] = _real2float(i_list[1])
                swifter_param['J4'] = _real2float(i_list[2])
            else:
                swifter_param['J2'] = 0.0
                swifter_param['J4'] = 0.0
            print(1, GMcb, file=plnew)
            print(0.0, 0.0, 0.0, file=plnew)
            print(0.0, 0.0, 0.0, file=plnew)
            line = plold.readline()  # Ignore the two zero vector lines
            line = plold.readline()
            for n in range(1, npl):  # Loop over planets
                line = plold.readline()
                i_list = [i for i in re.split('  +|\t',line) if i.strip()]
                GMpl = _real2float(i_list[0])
                if isSyMBA:
                    rhill = _real2float(i_list[1])
                    if swift_param['LCLOSE'] == "T":
                        plrad = _real2float(i_list[2])
                else:
                    if swift_param['LCLOSE'] == "T":
                        plrad = _real2float(i_list[1])
                if swifter_param['RHILL_PRESENT']:
                    print(n + 1, GMpl, rhill, file=plnew)
                else:
                    print(n + 1, GMpl, file=plnew)
                if swifter_param['CHK_CLOSE']:
                    print(plrad, file=plnew)
                line = plold.readline()
                i_list = [i for i in re.split('  +|\t',line) if i.strip()]
                rh = _real2float(i_list[0])
                yh = _real2float(i_list[1])
                zh = _real2float(i_list[2])
                print(rh, yh, zh, file=plnew)
                line = plold.readline()
                i_list = [i for i in re.split('  +|\t',line) if i.strip()]
                vhx = _real2float(i_list[0])
                vhy = _real2float(i_list[1])
                vhz = _real2float(i_list[2])
                print(vhx, vhy, vhz, file=plnew)
        plnew.close()
        plold.close()
    except IOError:
        print(f"Error converting PL file")
        
    # Convert the TP file
    if tpname == '':
        tpname = input("TP_IN: Name of new test particle input file: [tp.swifter.in]> ")
        if tpname == '':
            tpname = "tp.swifter.in"
    swifter_param['TP_IN'] = tpname

    try:
        tpnew = open(swifter_param['TP_IN'], 'w')
    except IOError:
        print(f"Cannot write to file {swifter_param['TP_IN']}")

    print(f"Converting TP file: {swift_param['TP_IN']} -> {swifter_param['TP_IN']}")
    try:
        print(f'Writing out new TP file: {swifter_param["TP_IN"]}')
        with open(swift_param['TP_IN'], 'r') as tpold:
            line = tpold.readline()
            i_list = [i for i in re.split('  +|\t',line) if i.strip()]
            ntp = int(i_list[0])
            print(ntp, file=tpnew)
            for n in range(0, ntp):  # Loop over test particles
                print(npl + n + 1, file=tpnew)
                line = tpold.readline()
                i_list = [i for i in re.split('  +|\t',line) if i.strip()]
                rh = _real2float(i_list[0])
                yh = _real2float(i_list[1])
                zh = _real2float(i_list[2])
                print(rh, yh, zh, file=tpnew)
                line = tpold.readline()
                i_list = [i for i in re.split('  +|\t',line) if i.strip()]
                vhx = _real2float(i_list[0])
                vhy = _real2float(i_list[1])
                vhz = _real2float(i_list[2])
                print(vhx, vhy, vhz, file=tpnew)
                # Ignore STAT lines
                line = tpold.readline()
                line = tpold.readline()
    except IOError:
        print(f"Error converting TP file")
    swifter_param['! VERSION'] = "Swifter parameter file converted from Swift"
    
    return swifter_param


def swifter2swiftest(swifter_param, plname="", tpname="", cbname="", conversion_questions={}):
    """
    Converts from a Swifter run to a Swiftest run

    Parameters
    ----------
    swifter_param : dictionary
       Swifter input parameters. 
    plname : string
        Name of massive body input file
    tpname : string
        Name of test particle input file
    cbname : string
        Name of central body input file
    conversion_questions : dictronary
        Dictionary of additional parameters required to convert between formats

    Returns
    -------
    swiftest_param : A set of input files for a new Swiftest run
    """

    swiftest_param = swifter_param.copy()
    # Pull additional feature status from the conversion_questions dictionary
    
    for key in newfeaturelist:
        swiftest_param[key] = conversion_questions.get(key, "NO")
    # Convert the PL file
    if plname == '':
        plname = input("PL_IN: Name of new planet input file: [pl.swiftest.in]> ")
        if plname == '':
            plname = "pl.swiftest.in"
    swiftest_param['PL_IN'] = plname
   
    try:
        plnew = open(swiftest_param['PL_IN'], 'w')
    except IOError:
        print(f"Cannot write to file {swiftest_param['PL_IN']}")
        return swifter_param

    print(f"Converting PL file: {swifter_param['PL_IN']} -> {swiftest_param['PL_IN']}")
    try:
        with open(swifter_param['PL_IN'], 'r') as plold:
            line = plold.readline()
            line = line.split("!")[0]  # Ignore comments
            i_list = [i for i in re.split('  +|\t',line) if i.strip()]
            npl = int(i_list[0])
            print(npl - 1, file=plnew)
            line = plold.readline()
            i_list = [i for i in re.split('  +|\t',line) if i.strip()]
            GMcb = _real2float(i_list[1])  # Store central body GM for later
            line = plold.readline()  # Ignore the two zero vector lines
            line = plold.readline()
            for n in range(1, npl):  # Loop over planets
                line = plold.readline()
                i_list = [i for i in re.split('  +|\t',line) if i.strip()]
                idnum = int(i_list[0])
                GMpl = _real2float(i_list[1])
                if swifter_param['RHILL_PRESENT']:
                   rhill = _real2float(i_list[2])
                   print(idnum, GMpl, rhill, file=plnew)
                else:
                   print(idnum, GMpl, file=plnew)
                if swifter_param['CHK_CLOSE']:
                    line = plold.readline()
                    i_list = [i for i in re.split('  +|\t',line) if i.strip()]
                    plrad = _real2float(i_list[0])
                    print(plrad, file=plnew)
                line = plold.readline()
                i_list = [i for i in re.split('  +|\t',line) if i.strip()]
                rh = _real2float(i_list[0])
                yh = _real2float(i_list[1])
                zh = _real2float(i_list[2])
                print(rh, yh, zh, file=plnew)
                line = plold.readline()
                i_list = [i for i in re.split('  +|\t',line) if i.strip()]
                vhx = _real2float(i_list[0])
                vhy = _real2float(i_list[1])
                vhz = _real2float(i_list[2])
                print(vhx, vhy, vhz, file=plnew)
        plnew.close()
        plold.close()
    except IOError:
        print(f"Error converting PL file")
        
    # Convert the TP file
    if tpname == '':
        tpname = input("TP_IN: Name of new planet input file: [tp.swiftest.in]> ")
        if tpname == '':
            tpname = "tp.swiftest.in"
    swiftest_param['TP_IN'] = tpname

    try:
        tpnew = open(swiftest_param['TP_IN'], 'w')
    except IOError:
        print(f"Cannot write to file {swiftest_param['TP_IN']}")
        return swifter_param
  
    print(f"Converting TP file: {swifter_param['TP_IN']} -> {swiftest_param['TP_IN']}")
    try:
        print(f'Writing out new TP file: {swiftest_param["TP_IN"]}')
        with open(swifter_param['TP_IN'], 'r') as tpold:
            line = tpold.readline()
            line = line.split("!")[0]  # Ignore comments
            i_list = [i for i in re.split('  +|\t',line) if i.strip()]
            ntp = int(i_list[0])
            print(ntp, file=tpnew)
            for n in range(0, ntp):  # Loop over test particles
                line = tpold.readline()
                i_list = [i for i in re.split('  +|\t',line) if i.strip()]
                name = int(i_list[0])
                print(name, file=tpnew)
                line = tpold.readline()
                i_list = [i for i in re.split('  +|\t',line) if i.strip()]
                rh = _real2float(i_list[0])
                yh = _real2float(i_list[1])
                zh = _real2float(i_list[2])
                print(rh, yh, zh, file=tpnew)
                line = tpold.readline()
                i_list = [i for i in re.split('  +|\t',line) if i.strip()]
                vhx = _real2float(i_list[0])
                vhy = _real2float(i_list[1])
                vhz = _real2float(i_list[2])
                print(vhx, vhy, vhz, file=tpnew)
        
        tpold.close()
        tpnew.close()
    except IOError:
        print(f"Error converting TP file")
        
    # Create the CB file
    if cbname == '':
        cbname = input("CB_IN: Name of new planet input file: [cb.swiftest.in]> ")
        if cbname == '':
            cbname = "cb.swiftest.in"
    swiftest_param['CB_IN'] = cbname
   
    unit_system = conversion_questions.get('UNITS', '')
    unit_type = 5
    if not unit_system:
        print(f"\nCentral body G*M = {GMcb}\n")
        print("Select the unit system to use:")
        print("1) MSun-AU-year")
        print("2) MSun-AU-day")
        print("3) SI: kg-m-s")
        print("4) CGS: g-cm-s")
        print("5) Set units manually")
        inval = input("> ")
        try:
            unit_type = int(inval)
        except ValueError:
            goodval = False
        else:
            goodval = (unit_type > 0 and unit_type < 6)
        if not goodval:
            print(f"{inval} is not a valid menu option")
            sys.exit(-1)
    if unit_type == 1 or unit_system.upper() == 'MSUN-AU-YEAR':
        print("Unit system is MSun-AU-year")
        swiftest_param['MU2KG'] = swiftest.MSun
        swiftest_param['DU2M'] = swiftest.AU2M
        swiftest_param['TU2S'] = swiftest.YR2S
    elif unit_type == 2 or unit_system.upper() == 'MSUN-AU-DAY':
        print("Unit system is MSun-AU-day")
        swiftest_param['MU2KG'] = swiftest.MSun
        swiftest_param['DU2M'] = swiftest.AU2M
        swiftest_param['TU2S'] = swiftest.JD2S
    elif unit_type == 3 or unit_system.upper() == 'SI':
        print("Unit system is SI: kg-m-s")
        swiftest_param['MU2KG'] = 1.0
        swiftest_param['DU2M'] = 1.0
        swiftest_param['TU2S'] = 1.0
    elif unit_type == 4 or unit_system.upper() == 'CGS':
        print("Unit system is CGS: g-cm-s")
        swiftest_param['MU2KG'] = 1e-3
        swiftest_param['DU2M'] = 1.0e-2
        swiftest_param['TU2S'] = 1.0
    elif unit_type == 5:
        print("User-defined units.")
        print("Define each unit (mass, distance, and time) by its corresponding SI value.")
        swiftest_param['MU2KG'] = input("Mass value in kilograms: ")
        swiftest_param['DU2M'] = input("Distance value in meters: ")
        swiftest_param['TU2S'] = input("Time unit in seconds: ")
    GU = swiftest.GC / (swiftest_param['DU2M'] ** 3 / (swiftest_param['MU2KG'] * swiftest_param['TU2S'] ** 2))
    print(f"Central body mass: {GMcb / GU} MU ({(GMcb / GU) * swiftest_param['MU2KG']} kg)")
   
    cbrad = conversion_questions.get('CBRAD', None)
    if not cbrad:
        print("Set central body radius:")
        print(f"1) Use Swifter parameter value of CHK_RMIN = {swifter_param['CHK_RMIN']}")
        print(f"2) Set value manually")
        inval = input("> ")
        try:
            cbrad_type = int(inval)
        except ValueError:
            goodval = False
        else:
            goodval = (cbrad_type > 0 and cbrad_type < 3)
        if not goodval:
            print(f"{inval} is not a valid menu option")
            sys.exit(-1)
        if cbrad_type == 1:
            cbrad = swifter_param['CHK_RMIN']
        elif cbrad_type == 2:
            cbrad = input("Enter radius of central body in simulation Distance Units: ")
            cbrad = _real2float(cbrad.strip())
    cbname = conversion_questions.get('CNAME', None)
    if not cbname:
        print("Set central body name:")
        cbname = input("> ")

    print(f'Writing out new CB file: {swiftest_param["CB_IN"]}')
    # Write out new central body file
    try:
        cbnew = open(swiftest_param['CB_IN'], 'w')
        print(cbname, file=cbnew)
        print(GMcb, file=cbnew)
        print(cbrad, file=cbnew)
        print(swifter_param['J2'], file=cbnew)
        print(swifter_param['J4'], file=cbnew)
        cbnew.close()
    except IOError:
        print(f"Cannot write to file {swiftest_param['CB_IN']}")
        return swifter_param
   
    GMTINY = conversion_questions.get('GMTINY', None)
    if not GMTINY:
        GMTINY = input(f"Value of GMTINY if this is a SyMBA simulation (enter nothing if this is not a SyMBA parameter file)> ")
    if GMTINY != '' and _real2float(GMTINY.strip()) > 0:
        swiftest_param['GMTINY'] = _real2float(GMTINY.strip())
        
    # Remove the unneeded parameters
    if 'C' in swiftest_param:
        swiftest_param['GR'] = True
        swiftest_param.pop('C', None)
    swiftest_param.pop('J2', None)
    swiftest_param.pop('J4', None)
    swiftest_param['IN_FORM'] = "XV"

    swiftest_param['DISCARD_OUT'] = conversion_questions.get('DISCARD_OUT', '')
    if not swiftest_param['DISCARD_OUT']:
        swiftest_param['DISCARD_OUT'] = input("DISCARD_OUT: Discard file name: [discard.out]> ")
        if swiftest_param['DISCARD_OUT'] == '':
            swiftest_param['DISCARD_OUT'] = "discard.out"

    swiftest_param['PARTICLE_OUT'] = conversion_questions.get('PARTICLE_OUT', '')
    if not swiftest_param['PARTICLE_OUT']:
        swiftest_param['PARTICLE_OUT'] = input("PARTICLE_OUT: Particle info file name (Only used by SyMBA): []> ")

    swiftest_param['! VERSION'] = "Swiftest parameter file converted from Swifter"
    return swiftest_param


def swift2swiftest(swift_param, plname="", tpname="", cbname="", conversion_questions={}):
    """
    Converts from a Swift run to a Swiftest run

    Parameters
    ----------
    swift_param : dictionary
       Swift input parameters. 
    plname : string
        Name of massive body input file
    tpname : string
        Name of test particle input file
    cbname : string
        Name of the central body input file
    conversion_questions : dictronary
        Dictionary of additional parameters required to convert between formats

    Returns
    -------
    swiftest_param : A set of input files for a new Swiftest run
    """
    if plname == '':
        plname = input("PL_IN: Name of new planet input file: [pl.swiftest.in]> ")
        if plname == '':
            plname = "pl.swiftest.in"
    pltmp = tempfile.NamedTemporaryFile()
    pltmpname = pltmp.name

    if tpname == '':
        tpname = input("TP_IN: Name of new planet input file: [tp.swiftest.in]> ")
        if tpname == '':
            tpname = "tp.swiftest.in"
    tptmp = tempfile.NamedTemporaryFile()
    tptmpname = tptmp.name

    # Create the CB file
    if cbname == '':
        cbname = input("CB_IN: Name of new planet input file: [cb.swiftest.in]> ")
        if cbname == '':
            cbname = "cb.swiftest.in"
    swifter_param = swift2swifter(swift_param, pltmpname, tptmpname, conversion_questions)
    swiftest_param = swifter2swiftest(swifter_param, plname, tpname, cbname, conversion_questions)
    swiftest_param['! VERSION'] = "Swiftest parameter file converted from Swift"
    return swiftest_param


def swiftest2swifter_param(swiftest_param, J2=0.0, J4=0.0):
    """
    Converts from a Swiftest run to a Swifter run

    Parameters
    ----------
    swiftest_param : dictionary
        Swiftest input parameters. 
    J2 : float
        Central body oblateness term. Default spherical.
    J4 : float
        Central body oblateness term. Default spherical.
        
    Returns
    -------
    swifter_param : A set of input files for a new Swifter run
    """
    swifter_param = swiftest_param
    CBIN = swifter_param.pop("CB_IN", None)
    GMTINY = swifter_param.pop("GMTINY", None)
    DISCARD_OUT = swifter_param.pop("DISCARD_OUT", None)
    MU2KG = swifter_param.pop("MU2KG", 1.0)
    DU2M = swifter_param.pop("DU2M", 1.0)
    TU2S = swifter_param.pop("TU2S", 1.0)
    GR = swifter_param.pop("GR", None)
    # if GR is not None:
    #     if GR:
    #        swifter_param['C'] =  swiftest.einsteinC * np.longdouble(TU2S) / np.longdouble(DU2M)
    for key in newfeaturelist:
       tmp = swifter_param.pop(key, None)
    if "ISTEP_DUMP" not in swifter_param:
        swifter_param["ISTEP_DUMP"] = swifter_param["ISTEP_OUT"]
    swifter_param['J2'] = J2
    swifter_param['J4'] = J4
    if swifter_param['OUT_STAT'] == "REPLACE":
        swifter_param['OUT_STAT'] = "UNKNOWN"
    if swifter_param['OUT_TYPE'] == 'NETCDF_DOUBLE':
       swifter_param['OUT_TYPE'] = 'REAL8'
    elif swifter_param['OUT_TYPE'] == 'NETCDF_FLOAT':
       swifter_param['OUT_TYPE'] = 'REAL4'
    if swifter_param['OUT_FORM'] == 'XVEL':
       swifter_param['OUT_FORM'] = 'XV'
    swifter_param['! VERSION'] = "Swifter parameter file converted from Swiftest"

    return swifter_param


def swifter2swift_param(swifter_param, J2=0.0, J4=0.0):
    """
    Converts from a Swifter run to a Swift run

    Parameters
    ----------
    swifter_param : dictionary
        Swifter input parameters. 
    J2 : float
        Central body oblateness term. Default spherical.
    J4 : float
        Central body oblateness term. Default spherical.
        
    Returns
    -------
    swift_param : A set of input files for a new Swift run
    """
    swift_param = {
        '! VERSION': f"Swift parameter input file converted from Swifter",
        'T0': 0.0,
        'TSTOP': 0.0,
        'DT': 0.0,
        'DTOUT': 0.0,
        'DTDUMP': 0.0,
        'L1': "F",
        'L1': "F",
        'L2': "F",
        'L3': "F",
        'L4': "F",
        'L5': "T",
        'L6': "F",
        'RMIN': -1,
        'RMAX': -1,
        'RMAXU': -1,
        'QMIN': -1,
        'LCLOSE': "F",
        'BINARY_OUTPUTFILE': "bin.dat",
        'STATUS_FLAG_FOR_OPEN_STATEMENTS': "NEW",
    }
    
    swift_param['T0'] = swifter_param['T0']
    swift_param['TSTOP'] = swifter_param['TSTOP']
    swift_param['DT'] = swifter_param['DT']
    # Convert the parameter file values
    swift_param['DTOUT'] = swifter_param['ISTEP_OUT'] * swifter_param['DT']
    swift_param['DTDUMP'] = swifter_param['ISTEP_DUMP'] * swifter_param['DT']
    swift_param['BINARY_OUTPUTFILE'] = swifter_param['BIN_OUT']
    swift_param['STATUS_FLAG_FOR_OPEN_STATEMENTS'] = swifter_param['OUT_STAT']

    if swifter_param['CHK_CLOSE']:
        swift_param['LCLOSE'] = "T"
    else:
        swift_param['LCLOSE'] = "F"

    swift_param['RMIN'] = swifter_param['CHK_RMIN']
    swift_param['RMAX'] = swifter_param['CHK_RMAX']
    swift_param['QMIN'] = swifter_param['CHK_QMIN']
    
    if swift_param['RMIN'] > 0 or swift_param['RMAX'] > 0 or swift_param['QMIN'] > 0:
        swift_param['L2'] = 'T'
        

    return swift_param
