import io
import os
import ast
import math
import pickle
import pyphot
import contextlib
import numpy as np
import pandas as pd
import dustmaps.sfd
import pkg_resources
from SEDFit.sed import SEDFit
from astropy import units as u
import matplotlib.pyplot as plt
from astropy.table import Table
from scipy.integrate import quad
from radpy.config import svopath
from astroARIADNE.star import Star
from astroquery.simbad import Simbad
from astroARIADNE.fitter import Fitter
from astropy import coordinates as coord
from radpy.stellar import check_if_string
from matplotlib.ticker import (MultipleLocator, FormatStrFormatter, AutoMinorLocator)

pyphot.config.set_units_backend('pint')
dustmaps.sfd.fetch()

import warnings
warnings.filterwarnings("ignore")

os.environ["CUDA_VISIBLE_DEVICES"] = "-1"  # Disable GPU
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"  #suppress everything but error messages
import tensorflow as tf

#%% Rewrite of the SEDFit functions
def definefilter(self, tmass=True, cousins=True, gaia=True, galex=True, johnson=True,
                 panstarrs=True, sdss=True, wise=True, xmm=True, spitzer=True, tycho=True, hip=True, tess=True,
                 stromgren=True,
                 new=True, empty=False, **kwargs):
    idx = []
    if new:
        for i in range(len(self.sed)):
            self.sed['sed_filter'][i] = self.sed['sed_filter'][i].replace(':', '.').replace('/', '.')
            self.sed['width'] = 0 * u.AA
        if tmass:
            if new:
                self.sed['sed_filter'] = self.sed['sed_filter'].astype(object)
                for i in range(len(self.sed)):
                    # self.sed['sed_filter'][i]=self.sed['sed_filter'][i].replace('Johnson.J','2MASS.J')
                    # self.sed['sed_filter'][i]=self.sed['sed_filter'][i].replace('Johnson.H','2MASS.H')
                    # self.sed['sed_filter'][i]=self.sed['sed_filter'][i].replace('Johnson.K','2MASS.Ks')
                    self.sed['sed_filter'][i] = self.sed['sed_filter'][i].replace('UKIDSS.J', '2MASS.J')
                    self.sed['sed_filter'][i] = self.sed['sed_filter'][i].replace('UKIDSS.H', '2MASS.H')
                    self.sed['sed_filter'][i] = self.sed['sed_filter'][i].replace('UKIDSS.Ks', '2MASS.Ks')
                    self.sed['sed_filter'][i] = self.sed['sed_filter'][i].replace('UKIDSS.K', '2MASS.Ks')
                    self.sed['sed_filter'][i] = self.sed['sed_filter'][i].replace('VISTA.J', '2MASS.J')
                    self.sed['sed_filter'][i] = self.sed['sed_filter'][i].replace('VISTA.H', '2MASS.H')
                    self.sed['sed_filter'][i] = self.sed['sed_filter'][i].replace('VISTA.Ks', '2MASS.Ks')
                    self.sed['sed_filter'][i] = self.sed['sed_filter'][i].replace('VISTA.K', '2MASS.Ks')
                    if self.sed['sed_filter'][i] == '2MASS:K': self.sed['sed_filter'][i] = '2MASS.Ks'
                self.sed['sed_filter'] = self.sed['sed_filter'].astype(str)

            filters = ['2MASS.J', '2MASS.H', '2MASS.Ks']
            width = np.array([0.152026, 0.241018, 0.250619]) / 2 * u.micron
            la = np.array([12393.09, 16494.95, 21638.61]) * u.AA
            ind = np.array([39, 41, 43])
            idx.extend(self.selectflux(filters, width, la, ind, new=new, empty=empty))
        if cousins:
            filters = ['Cousins.U', 'Cousins.B', 'Cousins.V', 'Cousins.R', 'Cousins.I']
            width = np.array([0.0657, 0.10117, 0.55014, 0.13811, 0.101107]) / 2 * u.micron
            la = np.array([3511.89, 4382.77, 5501.4, 6414.42, 7858.32]) * u.AA
            ind = np.array([7, 12, 24, 27, 33])
            idx.extend(self.selectflux(filters, width, la, ind, new=new, empty=empty))
        if gaia:
            filters = ['GAIA.GAIA3.G', 'GAIA.GAIA3.Gbp', 'GAIA.GAIA3.Grp']
            width = np.array([0.405297, 0.21575, 0.292444]) / 2 * u.micron
            la = np.array([6217.59, 5109.71, 7769.02]) * u.AA
            ind = np.array([28, 18, 34])
            idx.extend(self.selectflux(filters, width, la, ind, new=new, empty=empty))
        if galex:
            filters = ['GALEX.FUV', 'GALEX.NUV']
            width = np.array([0.026557, 0.076831]) / 2 * u.micron
            la = np.array([1535.08, 2300.78]) * u.AA
            ind = np.array([0, 3])
            idx.extend(self.selectflux(filters, width, la, ind, new=new, empty=empty))
        if johnson:
            filters = ['Johnson.U', 'Johnson.B', 'Johnson.V', 'Johnson.R', 'Johnson.I', 'Johnson.J', 'Johnson.K',
                       'Johnson.H']
            width = np.array([0.0657, 0.10117, 0.08898, 0.207, 0.2316, 0.319355, 0.5785, 0.286263]) / 2 * u.micron
            la = np.array([3511.89, 4382.77, 5501.4, 6819.05, 8657.44, 12317.3, 21735.85, 16396.38]) * u.AA
            ind = np.array([9, 13, 23, 29, 36, 40, 44, 42])
            idx.extend(self.selectflux(filters, width, la, ind, new=new, empty=empty))
        if panstarrs:
            filters = ['PAN-STARRS.PS1.g', 'PAN-STARRS.PS1.r', 'PAN-STARRS.PS1.i', 'PAN-STARRS.PS1.z',
                       'PAN-STARRS.PS1.y']
            width = np.array([0.105308, 0.125241, 0.120662, 0.099772, 0.063898]) / 2 * u.micron
            la = np.array([4849.11, 6201.2, 7534.96, 8674.2, 9627.79]) * u.AA
            ind = np.array([16, 25, 30, 35, 38])
            idx.extend(self.selectflux(filters, width, la, ind, new=new, empty=empty))
        if sdss:
            filters = ['SDSS.u', 'SDSS.g', 'SDSS.r', 'SDSS.i', 'SDSS.z']
            width = np.array([0.054097, 0.106468, 0.105551, 0.110257, 0.116401]) / 2 * u.micron
            la = np.array([3556.52, 4702.5, 6175.58, 7489.98, 8946.71]) * u.AA
            ind = np.array([8, 17, 26, 31, 37])
            idx.extend(self.selectflux(filters, width, la, ind, new=new, empty=empty))
        if wise:
            filters = ['WISE.W1', 'WISE.W2', 'WISE.W3', 'WISE.W4']
            width = np.array([0.662642, 1.042266, 5.505523, 4.10168]) / 2 * u.micron
            la = np.array([33682.21, 46179.06, 120718.12, 221944.04]) * u.AA
            ind = np.array([45, 48, 51, 53])
            idx.extend(self.selectflux(filters, width, la, ind, new=new, empty=empty))
        if xmm:
            filters = ['XMM-OT.V', 'XMM-OT.B', 'XMM-OT.U', 'XMM-OT.UVW1', 'XMM-OT.UVM2', 'XMM-OT.UVW2']
            width = np.array([0.069956, 0.091023, 0.067513, 0.074398, 0.046194, 0.04355]) / 2 * u.micron
            la = np.array([5450.47, 4368.97, 3465.51, 2895.36, 2284.66, 2041.68]) * u.AA
            ind = np.array([21, 14, 6, 4, 2, 1])
            idx.extend(self.selectflux(filters, width, la, ind, new=new, empty=empty))
        if spitzer:
            filters = ['Spitzer.IRAC.3.6', 'Spitzer.IRAC.4.5', 'Spitzer.IRAC.5.8', 'Spitzer.IRAC.8.0',
                       'Spitzer.MIPS.24']
            width = np.array([0.683618, 0.864992, 1.256117, 2.52885, 5.296286]) / 2 * u.micron
            la = np.array([35378.41, 44780.49, 56961.78, 77978.40, 235937.78]) * u.AA
            ind = np.array([46, 47, 49, 50, 52])
            idx.extend(self.selectflux(filters, width, la, ind, new=new, empty=empty))
        if hip:
            filters = ['Hipparcos.Hipparcos.Hp_MvB']
            width = np.array([0.240569]) / 2 * u.micron
            la = np.array([5338.25]) * u.AA
            ind = np.array([20])
            idx.extend(self.selectflux(filters, width, la, ind, new=new, empty=empty))
        if tycho:
            filters = ['TYCHO.TYCHO.B_MvB', 'TYCHO.TYCHO.V_MvB']
            width = np.array([0.074139, 0.113355]) / 2 * u.micron
            la = np.array([4194.96, 5300.19]) * u.AA
            ind = np.array([11, 19])
            idx.extend(self.selectflux(filters, width, la, ind, new=new, empty=empty))
        if tess:
            filters = ['TESS.TESS.Red']
            width = np.array([0.389865]) / 2 * u.micron
            la = np.array([7697.6]) * u.AA
            ind = np.array([32])
            idx.extend(self.selectflux(filters, width, la, ind, new=new, empty=empty))
        if stromgren:
            filters = ['Stromgren.u', 'Stromgren.v', 'Stromgren.b', 'Stromgren.y']
            width = np.array([0.037244, 0.022398, 0.020208, 0.02533]) / 2 * u.micron
            la = np.array([3443.6, 4105.46, 4666.25, 5475.17]) * u.AA
            ind = np.array([5, 10, 15, 22])
            idx.extend(self.selectflux(filters, width, la, ind, new=new, empty=empty))

        self.sed = self.sed[np.array(idx).flatten()]
        a = np.argsort(self.sed['la'])
        self.sed = self.sed[a]
        return


def downloadflux(self, userinput, deletevot=True, **kwargs):
    target = str(self.ra) + '%20' + str(self.dec)
    good = False
    if userinput is not None:
        self.sed = userinput
        # print("Using User input table")
        # print(self.sed)
        return
    else:
        if self.vizier_filename is not None:
            vot_fn = self.vizier_filename
            self.sed = Table.read(vot_fn)
            # print("Using VOT table")
            good = True
            if deletevot:
                os.remove(vot_fn)
        else:
            attempts, maxattempts = 0, 4
            while attempts < maxattempts:
                try:
                    target = str(self.ra) + '%20' + str(self.dec)
                    self.sed = Table.read(f"https://vizier.cds.unistra.fr/viz-bin/sed?-c={target}&-c.rs={self.radius}")
                    # print("Using vizier search")
                    good = True
                    break
                except:
                    attempts += 1
                    time.sleep(attempts ** 2)
        if not good:
            self.sed = []
            # print("Nothing worked")
            return

        self.sed['index'] = int(0)
        self.sed['la'] = self.sed['sed_freq'].to(u.AA, equivalencies=u.spectral())
        a = np.where((self.sed['la'] < 30 * u.micron) & (self.sed['la'] > 1000 * u.AA))[0]
        self.sed = self.sed[a]

        self.sed["sed_flux"] = self.sed["sed_flux"].to((u.erg / u.s / (u.cm ** 2) / u.AA),
                                                       equivalencies=u.spectral_density(self.sed['la'].data * u.AA))
        self.sed["sed_eflux"] = self.sed["sed_eflux"].to((u.erg / u.s / (u.cm ** 2) / u.AA),
                                                         equivalencies=u.spectral_density(self.sed['la'].data * u.AA))

        a = np.where(self.sed['sed_flux'] > 0)[0]
        self.sed = self.sed[a]

        a = np.where((np.isnan(self.sed["sed_eflux"]) == True) | (self.sed["sed_eflux"] / self.sed["sed_flux"] < 0.02))[
            0]
        self.sed["sed_eflux"][a] = self.sed["sed_flux"][a] * 0.02

        self.sed['eflux'] = self.sed["sed_eflux"] / self.sed["sed_flux"] / np.log(10)
        self.sed['flux'] = np.log10(self.sed["sed_flux"].value * self.sed['la'])

        self.definefilter(**kwargs)
        self.sed = self.sed[['index', 'sed_filter', 'la', 'width', 'flux', 'eflux']]
        # print("Using vizier photometry search")
        # print(self.sed)
        return


def set_quality(self):
    with open(pkg_resources.resource_filename('SEDFit', 'quality.p'), 'rb') as file:
        model = pickle.load(file)
    n = len(self.sed)
    input = np.zeros((n, 42, 2)) - 1
    input[:, self.sed['index'].astype(int), 0] = np.tile(np.max(self.sed['flux']) - self.sed['flux'], (n, 1))
    input[:, :, 1] = 0
    input[range(len(self.sed)), self.sed['index'], 1] = 1
    q = np.round(model.predict(input, verbose=0), 3)
    self.sed['valid'] = q[:, 3]
    self.sed['excess'] = q[:, 2]
    self.sed['bad'] = q[:, 1]
    a = np.where(q[:, 3] > 0.2)[0]
    if len(a) / n < 0.3:
        #print('Warning: large number of fluxes rejected, due to IR excess, noise, or misattribution. Manual vetting suggested')
        return False
    return

#%% Beginning of my own functions
def pull_coords(star_id, star, verbose=False):
    star_name = check_if_string(star_id, verbose=verbose)
    simbad_result = Simbad.query_object(star_name)
    radeg = simbad_result['ra'][0]
    decdeg = simbad_result['dec'][0]

    if verbose:
        print(f"\nCoordinates in decimal degrees for {star_name}:")
        print(f"RA: {radeg}, Dec: {decdeg}")

    coords = coord.SkyCoord(ra=radeg * u.deg, dec=decdeg * u.deg, frame='icrs')
    # Convert to sexagesimal string representation
    ra_sg = coords.ra.to_string(unit=u.hourangle, sep=':', precision=5)
    dec_sg = coords.dec.to_string(unit=u.deg, sep=':', precision=5)
    star.ra_deg = radeg
    star.dec_deg = decdeg
    star.ra_hms = ra_sg
    star.dec_dms = dec_sg
    if verbose:
        print(f"\nCoordinates in sexagesimal format for {star_name}:")
        print(f"RA: {ra_sg}, Dec: {dec_sg}")
    return radeg, decdeg, ra_sg, dec_sg

def pull_gaia_id(starid, star, verbose = False):
    star_name = check_if_string(starid, verbose = verbose)
    try:
            simbad_result = Simbad.query_objectids(star_name)
            if simbad_result is None:
                if verbose:
                    print(f"No results found in Simbad for {star_name}")
                    #d, dd = use_hipparcos(star_name, plx, dplx, verbose)
                #return round(d, 5), round(d, dd)

            # Find the column name for 'id' (case-insensitive)
            id_col = next((col for col in simbad_result.colnames if col.lower() == "id"), None)

            if id_col is not None:
                ids = simbad_result[id_col]
            else:
                raise KeyError("No column named 'id' or 'ID' found in simbad_result")

            gaiadr3mask = ['Gaia DR3' in name for name in ids]
            if any(gaiadr3mask):
                gaiadr3_name = ids[gaiadr3mask][0]
                if verbose:
                    print("Found Gaia DR3 ID:", gaiadr3_name)
                gdr3_id = gaiadr3_name.split()[-1]
                star.GaiaDR3ID = int(gdr3_id)
                return int(gdr3_id)

                # Only check DR2 if no DR3 found
            gaiadr2mask = ['Gaia DR2' in name for name in ids]
            if any(gaiadr2mask):
                gaiadr2_name = ids[gaiadr2mask][0]
                if verbose:
                    print("Found Gaia DR2 ID:", gaiadr2_name)
                gdr2_id = gaiadr2_name.split()[-1]
                star.GaiaDR2ID = int(gdr2_id)
                return int(gdr2_id)

            # Only check DR1 if no DR2 found
            gaiadr1mask = ['Gaia DR1' in name for name in ids]
            if any(gaiadr1mask):
                gaiadr1_name = ids[gaiadr1mask][0]
                if verbose:
                    print("Found Gaia DR1 ID:", gaiadr1_name)
                gdr1_id = gaiadr1_name.split()[-1]
                star.GaiaDR1ID = int(gdr1_id)
                return int(gdr1_id)
    except Exception as e:
        if verbose:
            print(f"Error in Simbad query: {e}")


def extract_photometry(starid, star, verbose=False):
    ra = star.ra_deg
    dec = star.dec_deg
    if hasattr(star, 'GaiaDR3ID'):
        if verbose:
            print('Star has Gaia DR3 ID.')
        gaia_id = star.GaiaDR3ID
    elif hasattr(star, 'GaiaDR2ID'):
        if verbose:
            print('Star has Gaia DR2 ID.')
        gaia_id = star.GaiaDR2ID
    elif hasattr(star, 'GaiaDR1ID'):
        if verbose:
            print('Star has Gaia DR1 ID.')
        gaia_id = star.GaiaDR1ID
    else:
        if verbose:
            print('Star does not have a Gaia ID')
        gaia_id = None
    if not verbose:
        f = io.StringIO()
        with contextlib.redirect_stdout(f):
            s = Star(starid, ra, dec, g_id=gaia_id, ignore=['SkyMapper', 'GALEX', 'APASS', 'Pan-STARRS', 'SDSS', 'ASCC', 'GLIMPSE', 'TESS', 'STROMGREN_PAUNZ', 'STROMGREN_HAUCK'], verbose=False)

    if verbose:
        s = Star(starid, ra, dec, g_id=gaia_id, ignore=['SkyMapper', 'GALEX', 'APASS', 'Pan-STARRS', 'SDSS', 'ASCC', 'GLIMPSE', 'TESS', 'STROMGREN_PAUNZ', 'STROMGREN_HAUCK'], verbose=False)

    return s

def inspect_photometry(phot_obj):
    phot_obj.print_mags()

def add_photometry(phot_obj, filter_name, mag, mag_err, verbose = False):
    filter_name = filter_name.lower()
    filt_map = {
        'gaia g': ('GaiaDR2v2_G'),
        'g': ('GaiaDR2v2_G'),
        'gaia gbp': ('GaiaDR2v2_BP'),
        'gbp': ('GaiaDR2v2_BP'),
        'gaia bp': ('GaiaDR2v2_BP'),
        'gaia grp': ('GaiaDR2v2_RP'),
        'grp': ('GaiaDR2v2_RP'),
        'gaia rp': ('GaiaDR2v2_RP'),
        '2mass k': ('2MASS_Ks'),
        '2massk': ('2MASS_Ks'),
        '2mass_k': ('2MASS_Ks'),
        '2mass j': ('2MASS_J'),
        '2massj': ('2MASS_J'),
        '2mass_j': ('2MASS_J'),
        '2mass h': ('2MASS_H'),
        '2massh': ('2MASS_H'),
        '2mass_h': ('2MASS_H'),
        'tycho bt': ('TYCHO_B_MvB'),
        'bt': ('TYCHO_B_MvB'),
        'tycho vt': ('TYCHO_V_MvB'),
        'vt': ('TYCHO_V_MvB'),
        'cousins r': ('GROUND_COUSINS_R'),
        'rc': ('GROUND_COUSINS_R'),
        'cousins i': ('GROUND_COUSINS_I'),
        'ic': ('GROUND_COUSINS_I'),
        'johnson u': ('GROUND_JOHNSON_U'),
        'u': ('GROUND_JOHNSON_U'),
        'johnson v': ('GROUND_JOHNSON_V'),
        'v': ('GROUND_JOHNSON_V'),
        'johnson b': ('GROUND_JOHNSON_B'),
        'b': ('GROUND_JOHNSON_B'),
        'stromgren u': ('STROMGREN_u'),
        'su': ('STROMGREN_u'),
        'stromgren v': ('STROMGREN_v'),
        'sv': ('STROMGREN_v'),
        'stromgren b': ('STROMGREN_b'),
        'sb': ('STROMGREN_b'),
        'stromgren y': ('STROMGREN_y'),
        'sy': ('STROMGREN_y'),
        'tess': ('TESS'),
        'tess t': ('TESS'),
        't': ('TESS'),
        'galex fuv': ('GALEX_FUV'),
        'gfuv': ('GALEX_FUV'),
        'galex nuv': ('GALEX_NUV'),
        'gnuv': ('GALEX_NUV'),
        'ps1 g': ('PS1_g'),
        'panstarrs g': ('PS1_g'),
        'ps1 r': ('PS1_r'),
        'panstarrs r': ('PS1_r'),
        'ps1 i': ('PS1_i'),
        'panstarrs i': ('PS1_i'),
        'ps1 z': ('PS1_z'),
        'panstarrs z': ('PS1_z'),
        'ps1 y': ('PS1_y'),
        'panstarrs y': ('PS1_y'),
        'sdss u': ('SDSS_u'),
        'sloan u': ('SDSS_u'),
        'sdss g': ('SDSS_g'),
        'sloan g': ('SDSS_g'),
        'sdss r': ('SDSS_r'),
        'sloan r': ('SDSS_r'),
        'sdss i': ('SDSS_i'),
        'sloan i': ('SDSS_i'),
        'sdss z': ('SDSS_z'),
        'sloan z': ('SDSS_z'),
        'wise w1': ('WISE_RSR_W1'),
        'w1': ('WISE_RSR_W1'),
        'wise w2': ('WISE_RSR_W2'),
        'w2': ('WISE_RSR_W2'),
        'wise w3': ('WISE_RSR_W3'),
        'w3': ('WISE_RSR_W3'),
        'wise w4': ('WISE_RSR_W4'),
        'w4': ('WISE_RSR_W4')
    }

    filt_name = filt_map.get(filter_name)
    if verbose:
        phot_obj.add_mag(mag, mag_err, filt_name)

    if not verbose:
        f = io.StringIO()
        with contextlib.redirect_stdout(f):
            phot_obj.add_mag(mag, mag_err, filt_name)

def remove_photometry(phot_obj, filter_name, verbose = False):
    filter_name = filter_name.lower()
    filt_map = {
        'gaia g': ('GaiaDR2v2_G'),
        'g': ('GaiaDR2v2_G'),
        'gaia gbp': ('GaiaDR2v2_BP'),
        'gbp': ('GaiaDR2v2_BP'),
        'gaia bp': ('GaiaDR2v2_BP'),
        'gaia grp': ('GaiaDR2v2_RP'),
        'grp': ('GaiaDR2v2_RP'),
        'gaia rp': ('GaiaDR2v2_RP'),
        '2mass k': ('2MASS_Ks'),
        '2massk': ('2MASS_Ks'),
        '2mass_k': ('2MASS_Ks'),
        '2mass j': ('2MASS_J'),
        '2massj': ('2MASS_J'),
        '2mass_j': ('2MASS_J'),
        '2mass h': ('2MASS_H'),
        '2massh': ('2MASS_H'),
        '2mass_h': ('2MASS_H'),
        'tycho bt': ('TYCHO_B_MvB'),
        'bt': ('TYCHO_B_MvB'),
        'tycho vt': ('TYCHO_V_MvB'),
        'vt': ('TYCHO_V_MvB'),
        'cousins r': ('GROUND_COUSINS_R'),
        'rc': ('GROUND_COUSINS_R'),
        'cousins i': ('GROUND_COUSINS_I'),
        'ic': ('GROUND_COUSINS_I'),
        'johnson u': ('GROUND_JOHNSON_U'),
        'u': ('GROUND_JOHNSON_U'),
        'johnson v': ('GROUND_JOHNSON_V'),
        'v': ('GROUND_JOHNSON_V'),
        'johnson b': ('GROUND_JOHNSON_B'),
        'b': ('GROUND_JOHNSON_B'),
        'stromgren v': ('STROMGREN_v'),
        'sv': ('STROMGREN_v'),
        'stromgren b': ('STROMGREN_b'),
        'sb': ('STROMGREN_b'),
        'stromgren y': ('STROMGREN_y'),
        'sy': ('STROMGREN_y'),
        'stromgren u': ('STROMGREN_u'),
        'su': ('STROMGREN_u'),
        'tess': ('TESS'),
        'tess t': ('TESS'),
        't': ('TESS'),
        'galex fuv': ('GALEX_FUV'),
        'gfuv': ('GALEX_FUV'),
        'galex nuv': ('GALEX_NUV'),
        'gnuv': ('GALEX_NUV'),
        'ps1 g': ('PS1_g'),
        'panstarrs g': ('PS1_g'),
        'ps1 r': ('PS1_r'),
        'panstarrs r': ('PS1_r'),
        'ps1 i': ('PS1_i'),
        'panstarrs i': ('PS1_i'),
        'ps1 z': ('PS1_z'),
        'panstarrs z': ('PS1_z'),
        'ps1 y': ('PS1_y'),
        'panstarrs y': ('PS1_y'),
        'sdss u': ('SDSS_u'),
        'sloan u': ('SDSS_u'),
        'sdss g': ('SDSS_g'),
        'sloan g': ('SDSS_g'),
        'sdss r': ('SDSS_r'),
        'sloan r': ('SDSS_r'),
        'sdss i': ('SDSS_i'),
        'sloan i': ('SDSS_i'),
        'sdss z': ('SDSS_z'),
        'sloan z': ('SDSS_z'),
        'wise w1': ('WISE_RSR_W1'),
        'w1': ('WISE_RSR_W1'),
        'wise w2': ('WISE_RSR_W2'),
        'w2': ('WISE_RSR_W2'),
        'wise w3': ('WISE_RSR_W3'),
        'w3': ('WISE_RSR_W3'),
        'wise w4': ('WISE_RSR_W4'),
        'w4': ('WISE_RSR_W4')
    }

    filt_name = filt_map.get(filter_name)
    if verbose:
        phot_obj.remove_mag(filt_name)

    if not verbose:
        f = io.StringIO()
        with contextlib.redirect_stdout(f):
            phot_obj.remove_mag(filt_name)


def save_photometry(starid, phot_obj, out_dir, verbose=False):
    try:
        os.makedirs(out_dir, exist_ok=True)
        print(f"Directory '{out_dir}' created successfully or already exists.")
    except OSError as e:
        print(f"Error creating directory {out_dir}: {e}")
    star_id = starid.replace(" ", "")
    f = Fitter()
    f.star = phot_obj
    f.out_folder = out_dir
    f.star.save_mags(f.out_folder + '/' + star_id)
    if verbose:
        print('File saved:', f.out_folder + '/' + star_id + 'mags.dat')
    fn = f.out_folder + '/' + star_id + 'mags.dat'
    return fn


def match_filters(filt_name, verbose=False):
    ###########################################################
    # Function: match_filters                                 #
    # Inputs:                                                 #
    #    filt_name: name of the filter bandpass               #
    #    verbose: if True, returns the print statements       #
    # Outputs:                                                #
    #    match: filter name in the svo format                 #
    #    sedmatch: filter name in the SEDFit format           #
    # How it works:                                           #
    #    1. reads in filter name and forces it all to be in   #
    #       lower case                                        #
    #    2. defines the filter map that takes the read in     #
    #       filter name and maps it to the correct format     #
    #    3. matches the input filter to the formats           #
    #    4. Returns the formatted name                        #
    ###########################################################

    filt_name = filt_name.lower()

    # Define the filter mapping dictionary
    filter_mapping = {
        'gaia g': ('GAIA/GAIA3.G', 'GAIA.GAIA3.G'),
        'g': ('GAIA/GAIA3.G', 'GAIA.GAIA3.G'),
        'gaiadr2v2_g': ('GAIA/GAIA3.G', 'GAIA.GAIA3.G'),
        'gaia gbp': ('GAIA/GAIA3.Gbp', 'GAIA.GAIA3.Gbp'),
        'gbp': ('GAIA/GAIA3.Gbp', 'GAIA.GAIA3.Gbp'),
        'gaia bp': ('GAIA/GAIA3.Gbp', 'GAIA.GAIA3.Gbp'),
        'gaiadr2v2_bp': ('GAIA/GAIA3.Gbp', 'GAIA.GAIA3.Gbp'),
        'gaia grp': ('GAIA/GAIA3.Grp', 'GAIA.GAIA3.Grp'),
        'grp': ('GAIA/GAIA3.Grp', 'GAIA.GAIA3.Grp'),
        'gaia rp': ('GAIA/GAIA3.Grp', 'GAIA.GAIA3.Grp'),
        'gaiadr2v2_rp': ('GAIA/GAIA3.Grp', 'GAIA.GAIA3.Grp'),
        '2mass k': ('2MASS/2MASS.Ks', '2MASS.Ks'),
        '2massk': ('2MASS/2MASS.Ks', '2MASS.Ks'),
        '2mass_k': ('2MASS/2MASS.Ks', '2MASS.Ks'),
        '2mass_ks': ('2MASS/2MASS.Ks', '2MASS.Ks'),
        '2mass j': ('2MASS/2MASS.J', '2MASS.J'),
        '2massj': ('2MASS/2MASS.J', '2MASS.J'),
        '2mass_j': ('2MASS/2MASS.J', '2MASS.J'),
        '2mass h': ('2MASS/2MASS.H', '2MASS.H'),
        '2massh': ('2MASS/2MASS.H', '2MASS.H'),
        '2mass_h': ('2MASS/2MASS.H', '2MASS.H'),
        'tycho bt': ('TYCHO/TYCHO.B_MvB', 'TYCHO.TYCHO.B_MvB'),
        'bt': ('TYCHO/TYCHO.B_MvB', 'TYCHO.TYCHO.B_MvB'),
        'tycho_b_mvb': ('TYCHO/TYCHO.B_MvB', 'TYCHO.TYCHO.B_MvB'),
        'tycho vt': ('TYCHO/TYCHO.V_MvB', 'TYCHO.TYCHO.V_MvB'),
        'vt': ('TYCHO/TYCHO.V_MvB', 'TYCHO.TYCHO.V_MvB'),
        'tycho_v_mvb': ('TYCHO/TYCHO.V_MvB', 'TYCHO.TYCHO.V_MvB'),
        'hipparcos hp': ('Hipparcos/Hipparcos.Hp_MvB', 'Hipparcos.Hipparcos.Hp_MvB'),
        'hp': ('Hipparcos/Hipparcos.Hp_MvB', 'Hipparcos.Hipparcos.Hp_MvB'),
        'cousins u': ('Generic/Cousins.U', 'Cousins.U'),
        'uc': ('Generic/Cousins.U', 'Cousins.U'),
        'cousins v': ('Generic/Cousins.V', 'Cousins.V'),
        'cv': ('Generic/Cousins.V', 'Cousins.V'),
        'cousins b': ('Generic/Cousins.B', 'Cousins.B'),
        'bc': ('Generic/Cousins.B', 'Cousins.B'),
        'cousins r': ('Generic/Cousins.R', 'Cousins.R'),
        'ground_cousins_r': ('Generic/Cousins.R', 'Cousins.R'),
        'rc': ('Generic/Cousins.R', 'Cousins.R'),
        'cousins i': ('Generic/Cousins.I', 'Cousins.I'),
        'ground_cousins_i': ('Generic/Cousins.I', 'Cousins.I'),
        'ic': ('Generic/Cousins.I', 'Cousins.I'),
        'johnson u': ('Generic/Johnson_UBVRIJHKL.U', 'Johnson.U'),
        'ground_johnson_u': ('Generic/Johnson_UBVRIJHKL.U', 'Johnson.U'),
        'u': ('Generic/Johnson_UBVRIJHKL.U', 'Johnson.U'),
        'johnson v': ('Generic/Johnson_UBVRIJHKL.V', 'Johnson.V'),
        'ground_johnson_v': ('Generic/Johnson_UBVRIJHKL.V', 'Johnson.V'),
        'v': ('Generic/Johnson_UBVRIJHKL.V', 'Johnson.V'),
        'johnson b': ('Generic/Johnson_UBVRIJHKL.B', 'Johnson.B'),
        'ground_johnson_b': ('Generic/Johnson_UBVRIJHKL.B', 'Johnson.B'),
        'b': ('Generic/Johnson_UBVRIJHKL.B', 'Johnson.B'),
        'johnson r': ('Generic/Johnson_UBVRIJHKL.R', 'Johnson.R'),
        'r': ('Generic/Johnson_UBVRIJHKL.R', 'Johnson.R'),
        'johnson i': ('Generic/Johnson_UBVRIJHKL.I', 'Johnson.I'),
        'i': ('Generic/Johnson_UBVRIJHKL.I', 'Johnson.I'),
        'johnson j': ('Generic/Johnson_UBVRIJHKL.J', 'Johnson.J'),
        'j': ('Generic/Johnson_UBVRIJHKL.J', 'Johnson.J'),
        'johnson k': ('Generic/Johnson_UBVRIJHKL.K', 'Johnson.K'),
        'k': ('Generic/Johnson_UBVRIJHKL.K', 'Johnson.K'),
        'johnson h': ('Generic/Johnson_UBVRIJHKL.H', 'Johnson.H'),
        'h': ('Generic/Johnson_UBVRIJHKL.H', 'Johnson.H'),
        'stromgren v': ('Generic/Stromgren.v', 'Stromgren.v'),
        'stromgren_v': ('Generic/Stromgren.v', 'Stromgren.v'),
        'stromgren u': ('Generic/Stromgren.u', 'Stromgren.u'),
        'stromgren_u': ('Generic/Stromgren.u', 'Stromgren.u'),
        'sv': ('Generic/Stromgren.v', 'Stromgren.v'),
        'stromgren b': ('Generic/Stromgren.b', 'Stromgren.b'),
        'stromgren_b': ('Generic/Stromgren.b', 'Stromgren.b'),
        'sb': ('Generic/Stromgren.b', 'Stromgren.b'),
        'stromgren y': ('Generic/Stromgren.y', 'Stromgren.y'),
        'stromgren_y': ('Generic/Stromgren.y', 'Stromgren.y'),
        'sy': ('Generic/Stromgren.y', 'Stromgren.y'),
        'tess': ('TESS/TESS.Red', 'TESS.TESS.Red'),
        'tess t': ('TESS/TESS.Red', 'TESS.TESS.Red'),
        't': ('TESS/TESS.Red', 'TESS.TESS.Red'),
        'galex fuv': ('GALEX/GALEX.FUV', 'GALEX.FUV'),
        'galex_fuv': ('GALEX/GALEX.FUV', 'GALEX.FUV'),
        'gfuv': ('GALEX/GALEX.FUV', 'GALEX.FUV'),
        'galex nuv': ('GALEX/GALEX.NUV', 'GALEX.NUV'),
        'galex_nuv': ('GALEX/GALEX.NUV', 'GALEX.NUV'),
        'gnuv': ('GALEX/GALEX.NUV', 'GALEX.NUV'),
        'ps1 g': ('PAN-STARRS/PS1.g', 'PAN-STARRS.PS1.g'),
        'ps1_g': ('PAN-STARRS/PS1.g', 'PAN-STARRS.PS1.g'),
        'panstarrs g': ('PAN-STARRS/PS1.g', 'PAN-STARRS.PS1.g'),
        'ps1 r': ('PAN-STARRS/PS1.r', 'PAN-STARRS.PS1.r'),
        'ps1_r': ('PAN-STARRS/PS1.r', 'PAN-STARRS.PS1.r'),
        'panstarrs r': ('PAN-STARRS/PS1.r', 'PAN-STARRS.PS1.r'),
        'ps1 i': ('PAN-STARRS/PS1.i', 'PAN-STARRS.PS1.i'),
        'ps1_i': ('PAN-STARRS/PS1.i', 'PAN-STARRS.PS1.i'),
        'panstarrs i': ('PAN-STARRS/PS1.i', 'PAN-STARRS.PS1.i'),
        'ps1 z': ('PAN-STARRS/PS1.z', 'PAN-STARRS.PS1.z'),
        'ps1_z': ('PAN-STARRS/PS1.z', 'PAN-STARRS.PS1.z'),
        'panstarrs z': ('PAN-STARRS/PS1.z', 'PAN-STARRS.PS1.z'),
        'ps1 y': ('PAN-STARRS/PS1.y', 'PAN-STARRS.PS1.y'),
        'ps1_y': ('PAN-STARRS/PS1.y', 'PAN-STARRS.PS1.y'),
        'panstarrs y': ('PAN-STARRS/PS1.y', 'PAN-STARRS.PS1.y'),
        'sdss u': ('SLOAN/SDSS.u', 'SDSS.u'),
        'sdss_u': ('SLOAN/SDSS.u', 'SDSS.u'),
        'sloan u': ('SLOAN/SDSS.u', 'SDSS.u'),
        'sdss g': ('SLOAN/SDSS.g', 'SDSS.g'),
        'sdss_g': ('SLOAN/SDSS.g', 'SDSS.g'),
        'sloan g': ('SLOAN/SDSS.g', 'SDSS.g'),
        'sdss r': ('SLOAN/SDSS.r', 'SDSS.r'),
        'sdss_r': ('SLOAN/SDSS.r', 'SDSS.r'),
        'sloan r': ('SLOAN/SDSS.r', 'SDSS.r'),
        'sdss i': ('SLOAN/SDSS.i', 'SDSS.i'),
        'sdss_i': ('SLOAN/SDSS.i', 'SDSS.i'),
        'sloan i': ('SLOAN/SDSS.i', 'SDSS.i'),
        'sdss z': ('SLOAN/SDSS.z', 'SDSS.z'),
        'sdss_z': ('SLOAN/SDSS.z', 'SDSS.z'),
        'sloan z': ('SLOAN/SDSS.z', 'SDSS.z'),
        'wise w1': ('WISE/WISE.W1', 'WISE.W1'),
        'wise_rsr_w1': ('WISE/WISE.W1', 'WISE.W1'),
        'w1': ('WISE/WISE.W1', 'WISE.W1'),
        'wise w2': ('WISE/WISE.W2', 'WISE.W2'),
        'wise_rsr_w2': ('WISE/WISE.W2', 'WISE.W2'),
        'w2': ('WISE/WISE.W2', 'WISE.W2'),
        'wise w3': ('WISE/WISE.W3', 'WISE.W3'),
        'wise_rsr_w3': ('WISE/WISE.W3', 'WISE.W3'),
        'w3': ('WISE/WISE.W3', 'WISE.W3'),
        'wise w4': ('WISE/WISE.W4', 'WISE.W4'),
        'wise_rsr_w4': ('WISE/WISE.W4', 'WISE.W4'),
        'w4': ('WISE/WISE.W4', 'WISE.W4'),
        'xmm v': ('XMM/OM.V_filter', 'XMM-OT.V'),
        'xmm b': ('XMM/OM.B_filter', 'XMM-OT.B'),
        'xmm u': ('XMM/OM.U_filter', 'XMM-OT.U'),
        'xmm w1': ('XMM/OM.UVW1_filter', 'XMM-OT.UVW1'),
        'xmm w2': ('XMM/OM.UVW2_filter', 'XMM-OT.UVW2'),
        'xmm m2': ('XMM/OM.UVM2_filter', 'XMM-OT.UVM2'),
        'spitzer irac 3.6': ('Spitzer/IRAC.I1', 'Spitzer.IRAC.3.6'),
        'irac 3.6': ('Spitzer/IRAC.I1', 'Spitzer.IRAC.3.6'),
        'spitzer irac 4.5': ('Spitzer/IRAC.I2', 'Spitzer.IRAC.4.5'),
        'irac 4.5': ('Spitzer/IRAC.I2', 'Spitzer.IRAC.4.5'),
        'spitzer irac 5.8': ('Spitzer/IRAC.I3', 'Spitzer.IRAC.5.8'),
        'irac 5.8': ('Spitzer/IRAC.I3', 'Spitzer.IRAC.5.8'),
        'spitzer irac 8.0': ('Spitzer/IRAC.I4', 'Spitzer.IRAC.8.0'),
        'irac 8.0': ('Spitzer/IRAC.I4', 'Spitzer.IRAC.8.0'),
        'spitzer mips 24': ('Spitzer/MIPS.24mu', 'Spitzer.MIPS.24'),
        'mips 24': ('Spitzer/MIPS.24mu', 'Spitzer.MIPS.24'),
    }

    # Find the corresponding match and sedmatch
    result = filter_mapping.get(filt_name)

    if result:
        match, sedmatch = result
        if verbose:
            print('Filter:', filt_name)
            print('Filter match:', match)
        return match, sedmatch

    # If filter name is not found, return None
    if verbose:
        print(f"No match found for filter: {filt_name}")
    return None, None

def set_filters(df, verbose = False):
    ##########################################################
    # Function: set_filters                                  #
    # Inputs:                                                #
    #    df: dataframe of photometry data                    #
    #    verbose: if True, returns an print statements       #
    # Outputs:                                               #
    #    df: same dataframe with new columns added           #
    # How it works:                                          #
    #    1. reads in the column 'Filter' to a variable       #
    #    2. in a for loop, calls the match_filters function  #
    #       which matches the input name to the correct      #
    #       format                                           #
    #    3. Creates new columns in the dataframd with the    #
    #       new filter names in the right formats            #
    #    4. Returns the dataframe                            #
    ##########################################################

    filter_name = df['Filter'].tolist()
    new_names = []
    sed_names = []
    for i in range(len(filter_name)):
        fn, sfn = match_filters(filter_name[i], verbose = verbose)
        new_names.append(fn)
        sed_names.append(sfn)
    df['Filter_name'] = new_names
    df['SED Filter name'] = sed_names
    return df


def get_zpts(df, verbose=False):
    ##########################################################
    # Function: get_zpts                                     #
    # Inputs:                                                #
    #    df: dataframe of photometry data                    #
    #    verbose: if True, returns an print statements       #
    # Outputs:                                               #
    #    df: same dataframe with new columns added           #
    # How it works:                                          #
    #    1. Reads in the svo filter data file                #
    #    2. Sets the columns needed to variables             #
    #    3. Runs through a for loop to compare the filter    #
    #       name to the svo filter data set                  #
    #    4. Pulls the zero point, reference wavelength,      #
    #       and effective width of the filter                #
    #    5. Adds the columns to the original dataframe       #
    #    6. Returns the dataframe                            #
    ##########################################################
    svo = pd.read_csv(svopath)
    svo_names = svo['Filter_name']
    svo_wref = svo['WavelengthRef']
    svo_weff = svo['WidthEff']
    svo_zpt = svo['ZPT (e-9)']

    filters = df['Filter_name']
    zpts = []
    zpt_err = []
    wref = []
    weff = []

    for i in range(len(filters)):
        name = filters[i]
        # print(name)
        for ii in range(len(svo_names)):
            if name == svo_names[ii]:
                if verbose:
                    print('Found a match.')
                    print('Input name:', name)
                    print('Matching:', svo_names[ii])
                zpts.append(svo_zpt[ii])
                zpt_err.append(0.05 * svo_zpt[ii])
                wref.append(svo_wref[ii])
                weff.append(svo_weff[ii])

    df['ZPT'] = zpts
    df['ZPT_err'] = zpt_err
    df['Ref wavelength'] = wref
    df['Effective width'] = weff

    return df


def read_in_photometry(filename, verbose=False):
    ##########################################################
    # Function: read_in_photometry                           #
    # Inputs:                                                #
    #    filename: filename of the photometry data           #
    #    verbose: if True, returns an print statements       #
    # Outputs:                                               #
    #    new_sed: astropy table                              #
    # How it works:                                          #
    #    1. Reads in the photometry file                     #
    #    2. Calls set_filters to re-format filter names      #
    #    3. Calls get_zpts to retrieve the svo filter info   #
    #    4. Separates out the needed data                    #
    #    5. Corrects the flux with the zero point            #
    #    6. Calculates the flux error                        #
    #    7. Creates a new dataframe with only needed data    #
    #    8. Converts new data frame to an astropy Table      #
    #    9. Converts the data into correct format for SEDFit #
    #   10. Adds units to data in Table.                     #
    #   11. Returns astropy Table in format for SEDFit       #
    ##########################################################
    if filename.endswith('.csv'):
        phot = pd.read_csv(filename)
    elif filename.endswith('.dat'):
        phot = pd.read_csv(filename, sep='\s+', skiprows=1, header=None)
        phot.columns = ['Filter', 'Magnitude', 'Error']

    phot1 = set_filters(phot)
    phot2 = get_zpts(phot1)
    mag = phot2['Magnitude']
    dmag = phot2['Error']
    zptf = phot2['ZPT'] * (1e-9)
    dzptf = phot2['ZPT_err'] * (1e-9)
    wave = phot2['Ref wavelength']
    pbwidth = phot['Effective width'] / 2

    flux = (zptf) * (10 ** (-0.4 * (mag)))
    # Calculating the error for the new fluxes
    term1 = (10 ** (-0.4 * mag)) * (dzptf)
    term2 = -0.4 * np.log(10) * (zptf) * (10 ** (-0.4 * mag)) * dmag
    flux_err = np.sqrt(term1 ** 2 + term2 ** 2)
    name = phot2['Filter']
    idx = np.arange(0, len(flux), 1)

    new_phot = pd.DataFrame(
        {'index': idx, 'sed_filter': phot['SED Filter name'], 'la': wave, 'width': pbwidth, 'flux': flux,
         'eflux': flux_err})
    new_sed = Table.from_pandas(new_phot)
    new_sed['la'].unit = u.AA
    new_sed['width'].unit = u.AA
    new_sed['flux'] = np.log10(flux * wave)
    new_sed['eflux'] = 0.434 * (flux_err / flux)
    new_sed['flux'].unit = u.erg / u.s / (u.cm ** 2) / u.AA
    new_sed['eflux'].unit = u.erg / u.s / (u.cm ** 2) / u.AA

    if verbose:
        new_sed

    return new_sed


def chi2red(x, numfp, verbose=False):
    ##########################################################
    # Function: chi2red                                      #
    # Inputs:                                                #
    #    x: sed object from SEDFit                           #
    #    numfp: number of fit parameters                     #
    #    verbose: if True, returns an print statements       #
    # Outputs:                                               #
    #    chi: chi squared value                              #
    #    chi2r: chi squared reduced value                    #
    # How it works:                                          #
    #    1. Sets the index range                             #
    #    2. Sets the "modeled" component to the synthetic    #
    #       fluxes from the model                            #
    #    3. Sets the "real" component to the input fluxes    #
    #    4. Sets the "error" component to the input flux err #
    #    5. Calculates chi squared                           #
    #    6. Calcualtes chi squared reduced                   #
    #    7. Returns chi squared and chi squared reduced.     #
    ##########################################################
    idx = np.arange(0, len(x.sed['index']))
    # print(idx)
    m = x.mags[idx]
    # print(m)
    r = x.sed['flux'][idx]
    # print(r)
    e = x.sed['eflux'][idx]
    # print(e)
    l = len(idx)
    # print(l)

    chi = np.sum(((m - r) / e) ** 2)
    chi2r = np.sum(((m - r) / e) ** 2) / (l - numfp)
    if verbose:
        print("Chi squared:", chi)
        print("Chi squared reduced:", chi2r)
    return (chi, chi2r)

def randomize_photometry(sed):
    idx = sed['index']
    filt = sed['sed_filter']
    wl = sed['la']
    width = sed['width']
    f = sed['flux']
    df = sed['eflux']

    #rand_wl = np.random.normal(wl, width)
    rand_f = np.random.normal(f, df)

    new_phot = pd.DataFrame(
        {'index': idx, 'sed_filter': filt, 'la': wl, 'width': width, 'flux': rand_f,
         'eflux': df})
    new_sed = Table.from_pandas(new_phot)
    new_sed['la'].unit = u.AA
    new_sed['width'].unit = u.AA
    new_sed['flux'] = rand_f
    new_sed['eflux'] = df
    new_sed['flux'].unit = u.erg / u.s / (u.cm ** 2) / u.AA
    new_sed['eflux'].unit = u.erg / u.s / (u.cm ** 2) / u.AA
    return new_sed


def calc_fbol(x, unit):
    ##########################################################
    # Function: calc_fbol                                    #
    # Inputs:                                                #
    #    star: star object                                   #
    #    x: sed object                                       #
    #    unit: unit string                                   #
    # Outputs:                                               #
    #    result: bolometric flux                             #
    #    error: error on bolometric flux                     #
    # How it works:                                          #
    #    1. Calls convert to generate the model values       #
    #    2. Defines a "model" for integration purposes       #
    #    3. Integrates the model                             #
    #    4. Sets bolometric flux value and error to star     #
    #    5. Returns values.                                  #
    ##########################################################
    _, _, _, _, model_w, model_f, _ = convert(x, unit = unit)

    F1 = np.trapezoid(model_f, model_w)

    # testing the model's convergence
    wav_fine = np.linspace(model_w.min(), model_w.max(), 100 * len(model_w))
    f_fine = np.interp(wav_fine, model_w, model_f)
    F2 = np.trapezoid(f_fine, wav_fine)

    convergence = ((F1 - F2) / F2) * 100
    if convergence >= 0.1:
        print("Integration does not converge.")

    return F1

def select_bestfit(star, sed_list, chi2list, chi2redlist):
    min_c2r = min(chi2redlist)
    minidx = np.argmin(chi2redlist)
    sed_bf = sed_list[minidx]
    minc2 = chi2list[minidx]
    star.SEDchi2 = minc2
    star.SEDchi2red = min_c2r
    return sed_bf


def fit_sed(sed, star, initial_guess, num_iter, unit, model, teffrange=None, loggrange=None, fehrange=None, avrange = None, fitT=False, fit_logg=False,
            fit_feh=False, fit_av = False, verbose=False):
    ##########################################################
    # Function: fit_sed                                      #
    # Inputs:                                                #
    #    sed: sed object                                     #
    #    star: StellarParams() object                        #
    #    initial_guess: intitial guess for fit parameters    #
    #                   [ teff, logg, feh]                   #
    #    model: model keyword                                #
    #           'phoenix' -> Phoenix model                   #
    #           'btsettl' -> BT-SETTL model                  #
    #           'kurucz' -> Kurucz model                     #
    #           'coehlo' -> Coehlo model                     #
    #    teffrange: if fitting for teff, sets the range of   #
    #               teff values the model can search through #
    #    loggrange: if fitting for logg, sets the range of   #
    #               logg values the model can search through #
    #    fehrange: if fitting for feh, sets the range of feh #
    #              values the model can search through       #
    #    fitT: default is False, if True, fits for teff      #
    #    fit_logg: default is False, if True, fits for logg  #
    #    fit_feh: default is False, if True, fits for feh    #
    #    verbose: if True, returns an print statements       #
    # Outputs:                                               #
    #    x: sed object with fitted parameters in it          #
    #    if verbose is true, returns the print statements    #
    #    displaying fit parameter solutions                  #
    # How it works:                                          #
    #    1. Reads in distance, RA, and dec from the star     #
    #    2. Initializes SEDfit object                        #
    #    3. Downloads user input photometry file             #
    #    4. Calls set_quality function to fix the SED object #
    #    5. Sets initial guess                               #
    #    6. Adds the intial guesses                          #
    #    7. Sets ranges for the fit variables if any         #
    #    8. Calls SEDFit.fit to fit the SED                  #
    #    9. Determines number of fit params based on what is #
    #       being fitted                                     #
    #   10. Calculates chi squared and chi squared reduced   #
    #   11. Sets the params to the stellar object            #
    #   12. Returns the SED object                           #
    ##########################################################
    d = star.dist
    ra = star.ra_hms
    dec = star.dec_dms

    f = io.StringIO()
    with contextlib.redirect_stdout(f):
        try:
            x = SEDFit(ra, dec, 1, use_gaia_params=False, use_gaia_xp=False, grid_type=model)
            x.addguesses(r=[1])
        except AttributeError:
            x = SEDFit(ra, dec, 0.5, use_gaia_params=False, use_gaia_xp=False, grid_type=model)

    teff, logg, feh, av = initial_guess
    x.dist = d
    x.addguesses(teff=teff, logg=logg, feh=feh, av=av)

    if teffrange is not None:
        x.addrange(teff=teffrange)
    if loggrange is not None:
        x.addrange(logg=loggrange)
    if fehrange is not None:
        x.addrange(feh=fehrange)
    if avrange is not None:
        x.addrange(av=avrange)

    fbol = []
    fbol_err = []
    chi2s = []
    chi2reds = []
    seds = []

    for i in range(num_iter):
        sed_obj = x
        # if verbose:
        #    print("Starting fit #", i+1)
        rand_sed = randomize_photometry(sed)
        downloadflux(sed_obj, rand_sed)
        # downloadflux(sed_obj, sed)
        set_quality(sed_obj)
        f1 = io.StringIO()
        with contextlib.redirect_stdout(f1):
            sed_obj.fit(use_gaia=False, idx=np.arange(0, len(sed_obj.sed['index'])), fitdist=False, fitteff=fitT,
                        fitfeh=fit_feh,
                        fitlogg=fit_logg, fitav=fit_av, quality_check=False)

        numOparams = 1
        if fitT == True:
            numOparams += 1
        if fit_logg == True:
            numOparams += 1
        if fit_feh == True:
            numOparams += 1
        if fit_av == True:
            numOparams += 1

        chi2, chi2r = chi2red(sed_obj, numOparams, verbose=False)
        chi2s.append(chi2)
        chi2reds.append(chi2r)

        fb = calc_fbol(sed_obj, unit)
        fbol.append(fb)

        seds.append(sed_obj)
        # if verbose:
        #    print("Finished fit #", i+1)

    avg_fbol = np.mean(fbol)
    std_fbol = np.std(fbol)
    fbol_err = np.sqrt((std_fbol) ** 2 + (0.02 * avg_fbol) ** 2)
    precision = (fbol_err / avg_fbol) * 100
    if verbose:
        print('Bolometric flux: ', round((avg_fbol / 1e-8), 5), '+/-', round((fbol_err / 1e-8), 5),
              'x 10^{-8) erg/s/cm^2')
        print('Fbol precision: ', round(precision, 3), '%')
    star.fbol = round((avg_fbol / 1e-8), 5)
    star.fbol_err = round((fbol_err / 1e-8), 5)

    sed_best = select_bestfit(star, seds, chi2s, chi2reds)

    if fitT == True:
        star.SEDTeff = x.getteff()[0]
    if fit_logg == True:
        star.SEDlogg = x.getlogg()[0]
    if fit_feh == True:
        star.SEDfeh = x.getfeh()
    if fit_av == True:
        star.SEDAv = x.getav()

    if verbose:
        print("Distance: {} pc".format(sed_best.getdist()))
        print("AV: {} mag".format(sed_best.getav()))
        print("Radius: {} Rsun".format(sed_best.getr()[0]))
        print("Teff: {} K".format(sed_best.getteff()[0]))
        print("Log g: {} ".format(sed_best.getlogg()[0]))
        print("Fe/H: {}".format(sed_best.getfeh()))

    return sed_best


def convert(sed, unit):
    ##########################################################
    # Function: convert                                      #
    # Inputs:                                                #
    #    sed: sed object                                     #
    #    unit: string with what unit you want                #
    #          options are: 'micron' or 'AA'                 #
    # Outputs:                                               #
    #    lit_w: input wavelength                             #
    #    lit_f: input flux                                   #
    #    lit_dw: input wavelength error                      #
    #    lit_df: input flux error                            #
    #    model_w: model wavelength                           #
    #    model_f: model flux                                 #
    #    synth_f: model fluxes in the wavelength badpasses   #
    # How it works:                                          #
    #    1. Reads in the model wavelength and converts to    #
    #       microns if needed. Default is angstroms          #
    #    2. Reads in the model flux and converts it into     #
    #       flux from log10(flux/wavelength) and converts it #
    #       into micron units if needed. Default is angstrom #
    #    3. Reads in wavelength error and converts if needed #
    #       Default is angstroms                             #
    #    4. Reads in flux error and converts it from log     #
    #       and converts into microns if needed.             #
    #    5. Reads in model wavelengths and converts it to    #
    #       microns if needed.                               #
    #    6. Reads in model flux and converts it out of log   #
    #       and converts it to microns if needed             #
    #    7. Reads in synthetic fluxes and converts it out of #
    #       log and converts it into microns if needed       #
    #    8. Returns values                                   #
    ##########################################################
    w = sed.sed['la']
    f = sed.sed['flux']
    dw = sed.sed['width']
    df = sed.sed['eflux']
    sf = sed.mags
    mw = sed.la
    mf = sed.fx.flatten()
    if unit == 'AA':
        lit_w = w  # literature wavelengths
        lit_f = (10 ** f) / np.array(w)  # literature flux
        lit_dw = dw # literature wavelength error
        lit_df = (df / 0.434) * lit_f  # literature flux error

        model_w = mw  # model wavelength
        model_f = (10 ** mf) / mw  # model flux

        synth_f = (10 ** sf) / np.array(w)  # model fluxes for the wavelength

        # residuals = lit_f-synth_f

        return np.array(lit_w), np.array(lit_f), np.array(lit_dw), np.array(lit_df), np.array(model_w), np.array(model_f), np.array(synth_f)

    if unit == 'micron':
        lit_w = w * (1e-4)  # literature wavelengths
        lit_f = ((10 ** f) / np.array(w)) * (1e4)  # literature flux
        lit_dw = dw * (1e-4)  # literature wavelength error
        lit_df = (df / 0.434) * lit_f  # literature flux error

        model_w = mw * (1e-4)  # model wavelength
        model_f = ((10 ** mf) / np.array(mw)) * (1e4)  # model flux

        synth_f = ((10 ** sf) / np.array(w)) * (1e4)  # model fluxes for the wavelength

        # residuals = np.array(lit_f)-np.array(synth_f)

        return np.array(lit_w), np.array(lit_f), np.array(lit_dw), np.array(lit_df), np.array(model_w), np.array(
            model_f), np.array(synth_f)

def set_values(x, unit, logplot=False, fbol_lam=False, verbose=False):
    ##########################################################
    # Function: set_values                                   #
    # Inputs:                                                #
    #    x: sed object                                       #
    #    unit: unit string                                   #
    #    logPlot: default is False, if True, indicates the   #
    #             log flag                                   #
    #    fbol_lam: default is False, if True, indicates the  #
    #             fbol_lam is flag                           #
    # Outputs:                                               #
    #    litp_xvals: input wavelength                        #
    #    litp_yvals: input flux                              #
    #    litp_dxvals: input wavelength error                 #
    #    litp_dyvals: input flux error                       #
    #    model_xvals: model wavelength                       #
    #    model_yvals: model flux                             #
    #    synth_yvals: model fluxes in the wavelength         #
    #                 bandpasses                             #
    #   residuals: lit flux values minus synth values        #
    # How it works:                                          #
    #    1. Calls convert to generate the model values       #
    #    2. Based on the flags set, converts the values to   #
    #       match what the flags say                         #
    #       logplot: convert everything back into log10      #
    #       fbol_lam: multiply the flux by wavelength        #
    #    3. Returns values                                   #
    ##########################################################
    iwave, iflux, idwave, idflux, mw, mf, msf = convert(x, unit=unit)

    if logplot and fbol_lam:
        # print('set values: Log and lambda')
        model_xvals = np.log10(mw)
        model_yvals = np.log10(mf * mw)
        litp_xvals = np.log10(iwave)
        litp_yvals = np.log10(iwave * iflux)
        litp_dyvals = 0.434 * (idflux / iflux)
        litp_dxvals = 0.434 * (idwave / iwave)
        synth_yvals = np.log10(msf * iwave)
        res = litp_yvals - synth_yvals
        exp = 0

        return model_xvals, model_yvals, litp_xvals, litp_yvals, litp_dxvals, litp_dyvals, synth_yvals, res, exp

    if logplot and not fbol_lam:
        # print('set values Log and no lambda')
        model_xvals = np.log10(mw)
        model_yvals = np.log10(mf)
        litp_xvals = np.log10(iwave)
        litp_yvals = np.log10(iflux)
        litp_dyvals = 0.434 * (idflux / iflux)
        litp_dxvals = 0.434 * (idwave / iwave)
        synth_yvals = np.log10(msf)
        res = litp_yvals - synth_yvals
        exp = 0
        return model_xvals, model_yvals, litp_xvals, litp_yvals, litp_dxvals, litp_dyvals, synth_yvals, res, exp

    if not logplot and not fbol_lam:
        # print('set values No log and no lambda')
        number = iflux[0]
        _, exp = normalize_number(number)
        # print('Dividing by:', exp)
        model_xvals = mw
        model_yvals = mf / (10 ** exp)
        litp_xvals = iwave
        litp_yvals = iflux / (10 ** exp)
        litp_dyvals = idflux / (10 ** exp)
        litp_dxvals = idwave
        synth_yvals = msf / (10 ** exp)
        res = litp_yvals - synth_yvals

        return model_xvals, model_yvals, litp_xvals, litp_yvals, litp_dxvals, litp_dyvals, synth_yvals, res, exp

    if not logplot and fbol_lam:
        number = (iflux[0] * iwave[0])
        _, exp = normalize_number(number)
        # print('Dividing by:', exp)
        model_xvals = mw
        model_yvals = (mf * mw) / (10 ** exp)
        litp_xvals = iwave
        litp_yvals = (iflux * iwave) / (10 ** exp)
        litp_dyvals = (idflux) / (10 ** exp)
        litp_dxvals = idwave
        synth_yvals = (msf * iwave) / (10 ** exp)
        res = litp_yvals - synth_yvals

        return model_xvals, model_yvals, litp_xvals, litp_yvals, litp_dxvals, litp_dyvals, synth_yvals, res, exp


def normalize_number(num):
    ##########################################################
    # Function: normalize_number                             #
    # Inputs:                                                #
    #    num: number you want normalized                     #
    # Outputs:                                               #
    #    normalized_num: normalized number                   #
    #    exponent: exponent that value was normalized by     #
    # How it works:                                          #
    #    1. Determines the power of 10                       #
    #    2. Determines normalization factor                  #
    #    3. Normalizes the number                            #
    #    4. Returns the normalized number and exoponent      #
    ##########################################################
    # Determine the power of 10 (exponent, X)
    if num != 0:
        exponent = math.floor(math.log10(abs(num)))
    else:
        exponent = 0  # Special case when num is 0 to avoid math domain error

    # Calculate the normalization factor (1eX)
    factor = 10 ** exponent

    # Divide the number by 10^X
    normalized_num = num / factor

    return normalized_num, exponent

def setaxislabels(exp, unit, logplot = False, fbol_lam = False):
    ##########################################################
    # Function: setaxislabels                                #
    # Inputs:                                                #
    #    unit: string of unit wanted                         #
    #    fbol_lam: flag for fbol_lam                         #
    # Outputs:                                               #
    #    xlab: x axis label                                  #
    #    ylab: y axis label                                  #
    # How it works:                                          #
    #    1. Based on unit chosen, sets x label dependent on  #
    #       unit                                             #
    #    2. Based on unit chosen and if the fbol_lam flag    #
    #       has been set, sets y label                       #
    #    3. Returns x axis and y axis labels                 #
    ##########################################################
    if logplot:
        if fbol_lam:
            ylab = r'$\rm \lambda F_{\lambda}~[\frac{erg}{cm^{2}~s}$]'
        else:
            if unit == 'AA':
                ylab = r'$\rm F_{\lambda}~[\frac{erg}{cm^{2}~s~\AA}$]'
            elif unit == 'micron':
                ylab = r'$\rm F_{\lambda}~[\frac{erg}{cm^{2}~s~\mu m}$]'
        if unit == 'AA':
            xlab = r'$\rm Wavelength~[\AA]$'
        elif unit == 'micron':
            xlab = r'$\rm Wavelength~[\mu m]$'
        return xlab, ylab
    else:
        if fbol_lam:
            #print('Fbol lam')
            #print('Exponent:', exp)
            if exp < 0:
                #print('Exp < 0')
                ylab = rf'$\rm \lambda F_{{\lambda}}~[\times 10^{{{exp}}}~\frac{{\rm erg}}{{\rm cm^2~s}}]$'
            if unit == 'AA':
                #print('angstroms')
                xlab = r'$\rm Wavelength~[\AA]$'
            elif unit == 'micron':
                #print('Microns')
                xlab = r'$\rm Wavelength~[\mu m]$'
            return xlab, ylab
        else:
            #print('No fbol lam')
            #print('Exoponent:', exp)
            if unit == 'AA':
                #print('Angstroms')
                xlab = r'$\rm Wavelength~[\AA]$'
                if exp < 0:
                    #print('exp < 0')
                    ylab = rf'$\rm F_{{\lambda}}~[\times 10^{{{exp}}}~\frac{{\rm erg}}{{\rm cm^2~s~\AA}}]$'
            elif unit == 'micron':
                #print('Microns')
                xlab = r'$\rm Wavelength~[\mu m]$'
                if exp < 0:
                    #print('Exp < 0')
                    ylab = rf'$\rm F_{{\lambda}}~[\times 10^{{{exp}}}~\frac{{\rm erg}}{{\rm cm^2~s~\mu m}}]$'
            return xlab, ylab


def set_radpy_axis_limits(w, f, exp, unit, logplot):
    ##########################################################
    # Function: set_axis_labels                              #
    # Inputs:                                                #
    #    mw: model wavelength array                          #
    #    mf: model flux array                                #
    #    unit: string of unit wanted                         #
    #    fbol_lam: flag for fbol_lam                         #
    # Outputs:                                               #
    #    set_axis: the axis limits in the format of          #
    #              [xmin, xmax, ymin, ymax]                  #
    # How it works:                                          #
    #    1. Based on unit chosen and fbol_lam flag setting,  #
    #       sets the axis limits based on the minimum of the #
    #       arrays  and maxs of the arrays                   #
    #    2. Returns the sxis limits                          #
    ##########################################################
    xmin = min(w)
    xmax = max(w)
    ymin = min(f)
    ymax = max(f)

    if unit == 'AA':
        if logplot:
            Xmin = xmin - (xmin * 0.05)
            Xmax = xmax + (xmax * 0.05)
            Ymin = ymin + (ymin * 0.05)
            Ymax = ymax - (ymax * 0.05)

            new_axis = [round(Xmin, 1), round(Xmax, 1), round(Ymin) * (10 ** (exp)), round(Ymax) * (10 ** (exp))]

            return new_axis
        else:
            Xmin = xmin - (xmin * 0.1)
            Xmax = xmax + (xmax * 0.1)
            Ymin = ymin + (ymin * 0.1)
            Ymax = ymax + (ymax * 0.2)

            new_axis = [round(Xmin, -2), round(Xmax, -3), round(Ymin, 1) * (10 ** (exp)), round(Ymax) * (10 ** (exp))]

            return new_axis
    if unit == 'micron':
        if logplot:
            Xmin = xmin - (xmin * 0.05)
            Xmax = xmax + (xmax * 0.05)
            Ymin = ymin + (ymin * 0.01)
            Ymax = ymax - (ymax * 0.01)

            new_axis = [round(Xmin, 2), round(Xmax, 2), round(Ymin) * (10 ** (exp)), round(Ymax) * (10 ** (exp))]

            return new_axis
        else:
            Xmin = xmin - (xmin * 0.05)
            Xmax = xmax + (xmax * 0.05)
            Ymin = ymin - (ymin * 0.05)
            Ymax = ymax + (ymax * 0.1)

            new_axis = [round(Xmin, 2), round(Xmax, 2), round(Ymin, 1) * (10 ** (exp)), round(Ymax, 1) * (10 ** (exp))]

            return new_axis


def setaxisticklabels(iwave, iflux, exp, unit, set_axis, logplot=False, fbol_lam=False, verbose=False):
    if set_axis:
        xmin = set_axis[0]
        xmax = set_axis[1]
        ymin = set_axis[2]
        ymax = set_axis[3]
    elif set_axis is None:
        set_axis = set_radpy_axis_limits(iwave, iflux, exp, unit, logplot=logplot)
        xmin = set_axis[0]
        xmax = set_axis[1]
        ymin = set_axis[2]
        ymax = set_axis[3]

    if logplot:
        xmin = round((xmin), 1)
        xmax = round((xmax), 1)
        ymin = round(ymin)
        ymax = round(ymax)
        # y ticks
        y_loc = [ymin, ymax]
        y_labels = [rf'$10^{{{y_loc[0]}}}$', rf'$10^{{{y_loc[1]}}}$']

        # x ticks
        xaxis = np.linspace(xmin, xmax, 5)
        # xaxis is in np.log10 space
        x_loc = []
        xl = []
        for i in range(len(xaxis)):
            # converting out of log10 space
            xloc = (10 ** (xaxis[i]))
            if unit == 'AA':
                # xlabel is out of log10 space
                xl.append(int((round(xloc, -3))))
                # xlocation is in log10 space
                x_loc.append(np.log10((round(xloc, -3))))
            if unit == 'micron':
                if xloc < 0.8:
                    # xlabel is out of log10 space
                    xl.append(round(xloc, 1))
                    # xlocation is in log10 space
                    x_loc.append(np.log10(round(xloc, 1)))
                else:
                    # xlabel is out of log10 space
                    xl.append(round(xloc))
                    # xlocation is in log10 space
                    x_loc.append(np.log10(round(xloc)))

        x_labels = [rf'$\rm {(val)}$' for val in xl]
        return x_loc, x_labels, y_loc, y_labels
    else:
        # y axis limits are in 10^exp space
        if fbol_lam:
            # y ticks
            # taking the yvals out of 10^exp space
            yloc = np.linspace(ymin / (10 ** exp), ymax / (10 ** exp), 4)
            y_loc = [round(val) for val in yloc]
            y_labels = [rf'$\rm {round(val)}$' for val in y_loc]

            # x ticks
            xaxis = np.linspace(xmin, xmax, 5)
            x_loc = []
            xl = []
            for i in range(len(xaxis)):
                xloc = xaxis[i]
                if unit == 'AA':
                    xl.append(int((round(xloc, -3))))
                    x_loc.append((round(xloc, -3)))
                if unit == 'micron':
                    if xloc < 1 and xloc > 0:
                        xl.append(round(xloc, 2))
                        x_loc.append(round(xloc, 2))
                    elif xloc == 0:
                        xl.append(round(xloc))
                        x_loc.append(round(xloc))
                    else:
                        if xloc % 1 < 0.5:
                            xl.append(round(np.floor(xloc)))
                            x_loc.append(round(np.floor(xloc)))
                        elif xloc % 1 > 0.5:
                            xl.append(round((xloc)))
                            x_loc.append(round((xloc)))

            x_labels = [rf'${(val)}$' for val in xl]
            return x_loc, x_labels, y_loc, y_labels
        else:
            # y ticks
            yloc = np.linspace(1, ymax / (10 ** exp), 4)
            y_loc = [round(val) for val in yloc]
            y_labels = [rf'$ \rm {round(val)}$' for val in y_loc]
            # x ticks
            xaxis = np.linspace(xmin, xmax, 5)
            x_loc = []
            xl = []
            for i in range(len(xaxis)):
                xloc = xaxis[i]
                if unit == 'AA':
                    xl.append(int((round(xloc, -3))))
                    x_loc.append((round(xloc, -3)))
                if unit == 'micron':
                    if xloc < 1 and xloc > 0:
                        xl.append(round(xloc, 2))
                        x_loc.append(round(xloc, 2))
                    elif xloc == 0:
                        xl.append(round(xloc))
                        x_loc.append(round(xloc))
                    else:
                        if xloc % 1 < 0.5:
                            xl.append(round(np.floor(xloc)))
                            x_loc.append(round(np.floor(xloc)))
                        elif xloc % 1 > 0.5:
                            xl.append(round((xloc)))
                            x_loc.append(round((xloc)))

            x_labels = [rf'${(val)}$' for val in xl]
            return x_loc, x_labels, y_loc, y_labels


def setaxislimits(iwave, iflux, exp, unit, set_axis, logplot=False, fbol_lam=False):
    if set_axis:
        xmin = set_axis[0]
        xmax = set_axis[1]
        ymin = set_axis[2]
        ymax = set_axis[3]
    else:
        set_axis = set_radpy_axis_limits(iwave, iflux, exp, unit, logplot=logplot)
        xmin = set_axis[0]
        xmax = set_axis[1]
        ymin = set_axis[2]
        ymax = set_axis[3]

    if logplot:
        return xmin - 0.1, xmax + 0.1, ymin - 0.5, ymax + 0.1
    else:
        if unit == 'AA':
            return xmin - 500, xmax + 500, (ymin / (10 ** (exp))) - 0.25, (ymax / (10 ** (exp))) + 0.25
        if unit == 'micron':
            return xmin - 0.1, xmax + 0.1, (ymin / (10 ** (exp))) - 0.25, (ymax / (10 ** (exp))) + 0.25

def set_res_axis(res, logplot = False):
    min_res = np.min(res)
    max_res = np.max(res)

    minres = round(min_res, 1)
    maxres = round(max_res, 1)

    if abs(minres) >= abs(maxres):
        if logplot:
            res_axis_min = (abs(minres)+0.05)*-1
            res_axis_max = abs(minres)+0.05
            res_loc = [abs(minres)*-1, 0, abs(minres)]
            res_labels = [rf'$\rm {val}$' for val in res_loc]
        else:
            res_axis_min = (abs(minres)+0.5)*-1
            res_axis_max = abs(minres)+0.5
            res_loc = [round(abs(minres)*-1), 0, round(abs(minres))]
            res_labels = [rf'$\rm {val}$' for val in res_loc]
    elif abs(minres) <= abs(maxres):
        if logplot:
            res_axis_min = (abs(maxres)+0.05)*-1
            res_axis_max = abs(maxres)+0.05
            res_loc = [abs(maxres)*-1, 0, abs(maxres)]
            res_labels = [rf'$\rm {val}$' for val in res_loc]
        else:
            res_axis_min = (abs(maxres)+0.5)*-1
            res_axis_max = abs(maxres)+0.5
            res_loc = [round(abs(maxres)*-1), 0, round(abs(maxres))]
            res_labels = [rf'$\rm {val}$' for val in res_loc]

    return res_axis_min, res_axis_max, res_loc, res_labels

def plot_sed(x, unit, logplot=True, fbol_lam=True, set_axis=None, title=None, savefig=None, uselatex = False, show=True, verbose=False):
    ##########################################################
    # Function: plot_sed                                     #
    # Inputs:                                                #
    #    x: sed object                                       #
    #    unit: unit chosen                                   #
    #    logplot: log flag                                   #
    #             if True, sets plot in log space            #
    #    fbol_lam: fbol_lam flag                             #
    #             if True, multiplies the flux by wavelength #
    #    set_axis: allows user to set their axis limits      #
    #             if None, will set based on the model vals  #
    #    title: allows user to set the plot title            #
    #    savefig: allows user to save fig                    #
    #            give a filename                             #
    #    show: shows Figure                                  #
    # Outputs:                                               #
    #    displays the plot                                   #
    # How it works:                                          #
    #    1. Determines the axis limits based on user input   #
    #       and flags set                                    #
    #    2. Calls set_values to generate the data to be      #
    #       plotted                                          #
    #    3. Plots everything                                 #
    ##########################################################

    #iwave, iflux, idwave, idflux, mw, mf, msf = convert(x, unit)

    plt.rcParams.update({'font.size': 15})
    plt.rcParams['xtick.direction'] = 'in'
    plt.rcParams['ytick.direction'] = 'in'
    plt.rcParams['text.usetex'] = uselatex

    f, axes = plt.subplots(2, 1, gridspec_kw={'height_ratios': [10, 3]}, sharex=True)

    model_xvals, model_yvals, litp_xvals, litp_yvals, litp_dxvals, litp_dyvals, synth_yvals, res, exp = set_values(x, unit,
                                                                                                                   logplot=logplot,
                                                                                                                   fbol_lam=fbol_lam,
                                                                                                                   verbose=verbose)
    xmin, xmax, ymin, ymax = setaxislimits(litp_xvals, litp_yvals, exp, unit, set_axis, logplot=logplot, fbol_lam=fbol_lam)
    xloc, xlabels, yloc, ylabels = setaxisticklabels(litp_xvals, litp_yvals, exp, unit, set_axis, logplot=logplot, fbol_lam=fbol_lam)

    axes[0].set_ylim(ymin, ymax)
    axes[1].set_xlim(xmin, xmax)
    axes[0].set_yticks(yloc)
    axes[0].set_yticklabels(ylabels)
    axes[1].set_xticks(xloc)
    axes[1].set_xticklabels(xlabels)

    axes[0].plot(model_xvals, model_yvals, 'g', linewidth=1, label=r'$\rm Model~Spectrum$')
    axes[0].plot(litp_xvals, litp_yvals, 'b.', markersize=10, markerfacecolor='none', label=r'$\rm Photometry$')
    axes[0].errorbar(litp_xvals, litp_yvals, xerr=litp_dxvals, yerr=litp_dyvals, fmt='.', markerfacecolor='none',
                     color='blue', capsize=3)
    axes[0].plot(litp_xvals, synth_yvals, 'r.', markersize=10, label=r'$\rm Synthetic~Photometry $')

    axes[0].legend(prop={'size': 10}, loc='best')
    axes[0].tick_params(axis='x', labelbottom=False)

    axes[1].plot(litp_xvals, res, 'k.')
    axes[1].errorbar(litp_xvals, res, yerr=litp_dyvals, fmt='.', color='black')
    axes[1].axhline(y=0)
    ramin, ramax, rloc, rlabel = set_res_axis(res, logplot = logplot)
    axes[1].set_ylim(ramin, ramax)
    axes[1].set_yticks(rloc)
    axes[1].set_yticklabels(rlabel)
    if logplot:
        if unit == 'micron':
            axes[1].set_ylabel(r'$\rm Residuals$', labelpad=5)
        if unit == 'AA':
            axes[1].set_ylabel(r'$\rm Residuals$', labelpad=5)
    else:
        if unit == 'micron':
            axes[1].set_ylabel(r'$\rm Residuals$', labelpad=0)
        if unit == 'AA':
            axes[1].set_ylabel(r'$\rm Residuals$', labelpad=0)

    xlab, ylab = setaxislabels(exp, unit, logplot=logplot, fbol_lam=fbol_lam)
    axes[1].set_xlabel(xlab)
    axes[0].set_ylabel(ylab)
    plt.subplots_adjust(wspace=0, hspace=0)
    axes[0].xaxis.set_minor_locator(AutoMinorLocator())
    axes[0].yaxis.set_minor_locator(AutoMinorLocator())
    axes[1].xaxis.set_minor_locator(AutoMinorLocator())
    axes[1].yaxis.set_minor_locator(AutoMinorLocator())

    if title:
        axes[0].set_title(title)
    if savefig:
        f.savefig(savefig, bbox_inches='tight')
    if show:
        plt.show()

    return f, axes
