# Copyright 2023 AUI, Inc. Washington DC, USA
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
"""
this module will be included in the api
"""

import traceback

def measures_available(measures_site=None, logger=None):
    """
    Return a list of available measures versions at measures_site.

    This returns a list of the measures versions available at measures_site.
    If measures_site is None, then the meausres_site config value is used.

    The list of available measures is sorted using the date and time fields of
    the file name so that the most recent file appears at the end of the list.

    The measures_site may be a single string value or a list of strings where
    each element is a URL where measures tar files are found.

    If measures_site is a list then the elements are used in order until
    an appropriate list of available measures is found (see more below). 
    If measures_site is not provide then the measures_site config value 
    is used.

    The first element of the returned list is always the measures site URL
    used to populate the list.

    The measures_site_interval config value may be used when determining which
    site to use if measures_site is a list. When stepping through all the
    elements of measures_site, if the most recent measures tar file at that 
    site has a date that is less than or equal to measures_site_interval 
    days before the current date then that list of measures tar files from 
    that site is returned. If none of the sites satisfy that criteria for 
    the most recent file then the list having the most recent tar file is 
    returned. 

    If the returned list is older than measures_site_interval days before
    the current date then a warning is logged and printed as determind
    by the casaconfig_verbose config value.

    When comparing the date of the most recent tar file with that of
    the current date, the date value (excluding the time) found in the
    tar file name is compared with the current date without any correction 
    for time zone differences. This is an approximate age of the most recent 
    tar file and is primiarly intended to identify a site that may not be 
    updating regularly (daily) as expected so that casaconfig can use another
    in the list of measures_site automatically.

    The list of available measures versions is the list of files at 
    measures_site that follow the pattern of *Measures*YYYYMMDD-HHMMSS*tar*, 
    excluding files that end in ".md5", where YYYYMMDD and HHMMSS are each 
    single digits (0 through 9).

    Note that the version parameter in measures_update must be an element in
    a list returned by measures_available so that measures_update can find
    the expected version. Note that measures tar file names will usually
    not be the same at different sites.

    Parameters
       - measures_site(str or list of str = None) - Each value is a URL where measures tar files are found. If measures_site is a list then the elements are used in order until a list can be assembled. Default None uses config.measures_site.
       - logger (casatools.logsink=None) - Instance of the casalogger to use for writing messages. Default None writes messages to the terminal. The value of config.casaconfig_verbose is used. The logger is only used if the last file in the returned list is more than config.measures_site_interval days before the current date.

    Returns
       list - version names returned as list of strings, the first element of this list is the is the site used. The file names are sorted by date and time as found in the name with the most recent name appearing at the end of the list.

    Raises
       - casaconfig.NoNetwork - Raised where there is no network seen, can not continue
       - casaconfig.RemoteError - Raised when there is an error fetching some remote content for some reason other than no network
       - ValueError - Raised when config.measures_site_interval can not be used as an int
       - Exception - Unexpected exception while getting list of available measures versions

    """
    from casaconfig import NoNetwork, RemoteError
    import urllib.error
    import re
    from datetime import date
    
    from .get_available_files import get_available_files
    from .print_log_messages import print_log_messages
    
    from .. import config as _config

    verbose = _config.casaconfig_verbose

    if measures_site is None:
        measures_site = _config.measures_site

    # this makes sure that measures_site_interval can be used as an int
    # this raises a ValueError if there's a problem
    measures_site_interval = int(_config.measures_site_interval)

    def measuresFileAge(measuresFileName):
        # return the age of a measuresFile, in days before today
        # do not attempt to correct for time zones
        # only uses the year, month, and day fields
        
        date_pattern = r".*_Measures_(\d{4})(\d{2})(\d{2})-.*"
        dateMatch = re.search(date_pattern, measuresFileName)
        fileDate = date(int(dateMatch.group(1)),int(dateMatch.group(2)),int(dateMatch.group(3)))
        dateDiff = date.today() - fileDate
        return dateDiff.days        

    if isinstance(measures_site, list):
        saved_exc = None
        # this list is only used if all of the sites in the list are out of date
        # and it is necessary to find the one that's least out of date.
        # the list is a tuple of (age, file_list) where age is the age, in days, of
        # the last entry in file_list and file_list is the list of files at that
        # site
        file_age_list = []

        saved_exc = None
        
        for this_site in measures_site:
            try:
                # turn off the logger here so that only this initial call can produce a logged message warning
                result = measures_available(this_site, logger=None)
                if len(result) > 1:
                    siteAge = measuresFileAge(result[-1])
                    # something useful can be returned, unset saved_exc
                    saved_exc = None
                    if siteAge <= measures_site_interval:
                        return (result)
                    # the only way to get here is when the last file in result is more than measures_site_interval days from today
                    file_age_list.append((siteAge,result))
            except RemoteError as exc:
                # save it, if it's still set when the loop exits, reraise it, only the last exception is reraised
                saved_exc = exc
            except NoNetwork as exc:
                # reraise this, there's no recovering from it
                raise exc
            except Exception as exc:
                # save it, if it's still set when the loop exits, reraise it, only the last exception is reraised
                print("exception when trying : " + this_site)
                print(str(exc))
                print(str(type(exc)))
                saved_exc = exc

        # if it gets here, either none of the sites had any result to use or
        # file_age_list has at least one entry
        # of there was an exception
        
        if len(file_age_list) > 0:
            # something can be returned
            # return the one with the smallest age
            thisAge = file_age_list[0][0]
            result = file_age_list[0][1]
            for age_tuple in file_age_list[1:]:
                if age_tuple[0] < thisAge:
                    thisAge = age_tuple[0]
                    result = age_tuple[1]
            # and log that that's what's going on, not an exception
            msgs = []
            msgs.append("Warning: the most recent measures tar file at each of the sites was older than config.measures_site_interval")
            msgs.append("%s had the most recent measures tar file, returning that list" % result[0])
            print_log_messages(msgs, logger, False, verbose)
            return(result)

        if saved_exc is None:
            # I don't think this is possible
            raise RemoteError("Unable to retrieve list of available measures versions, measures_site value may be an empty list or no files were found at any site, check and try again.")

        else:
            # saved exception, this is the most recent if multiple were raised, reraise it here
            # unsure what this should look like, need try to things out
            raise saved_exc from None

    else:
        # make sure it's used as a string
        measures_site = str(measures_site)

        # this pattern matches "<anything>_Measures_YYYYMMDD-HHMMSS.<anything>tar<anything>
        # where YYYY MM DD HH MM SS are all digits having exactly that number of digits.
        # "tar" must appear somewhere after the "." following the digits. This allows for
        # different compression schemes to be used and signaled in the name, so long as the
        # tarfile module can understand that compression. Note that get_available_tarfiles
        # also excludes files that end in "md5". That is currently only relevant for casarundata
        # but it may be an issue in some future site so that excludes those files.
        pattern = r".*_Measures_\d{8}-\d{6}\..*tar.*"
        
        try:
            files_list = get_available_files(measures_site, pattern)

            if len(files_list) == 0:
                # nothing found there, RemoteError
                # no exception, so the sites must have no files found
                raise RemoteError("Unable to retrieve list of available measures versions, measures_site value may be an empty list or no files were found at measures_site, check and try again.")

            # because the prefix changed during the development of the NRAO/casa measures
            # tarballs this list needs to be sorted, excluded everything before "Measures_".
            # it's probably not a bad idea in general, that keeps things in time sorted order

            def sort_after_Measures(text):
                # extract the substring after "Measures_" for sorting.
                return text.split("Measures")[1]
            result = sorted(files_list, key=sort_after_Measures)
            
            # and prepend the measures_site to the list
            result.insert(0,measures_site)

            # if the logger is set, check if the site appears to be too old and
            # log a warning if it is
            if logger is not None:
                siteAge = measuresFileAge(result[-1])
                if siteAge > measures_site_interval:
                    msgs = []
                    msgs.append("Warning: the most recent tar file at %s was older than config.measures_site_interval" % measures_site)
                    print_log_messages(msgs, logger, False, verbose)
                    
            return (result)
    
        except RemoteError as exc:
            # reraise this as is
            raise exc
        
        except NoNetwork as exc:
            # reraise this as is
            raise exc
    
        except urllib.error.URLError as urlerr:
            raise RemoteError("Unable to retrieve list of available measures versions : " + str(urlerr)) from None
        except UnicodeError as unicodeErr:
            raise RemoteError("Unable to retrieve list of available measures versions because of a UnicodeError, likely the site name is incorrect. Site : " + str(measures_site) + " error: " + str(unicodeErr)) from None
        except Exception as exc:
            msg = "Unexpected exception while getting list of available measures versions : " + str(exc)
            raise Exception(msg)

        # nothing to return if it got here, I don't think there's a way to get here, anything not already returning raises an exception
    return []
