/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <StdAfx.h>
#include <stdio.h>
#include <sys/stat.h>
#include <AzCore/std/time.h>
#include <string>
#include "RemoteFileIO.h"
#include "ProjectDefines.h"
#include "CryAssert.h"
#include <AzFramework/Asset/AssetProcessorMessages.h>
#include <AzFramework/Network/AssetProcessorConnection.h>
#include <AzCore/Serialization/Utils.h>

#include <AzCore/Math/Crc.h>
#include <AzCore/std/algorithm.h> // for GetMin()
#include <AzCore/std/parallel/lock.h>
#include <AzCore/std/functional.h> // for function<> in the find files callback.
#ifdef REMOTEFILEIO_CACHE_FILETREE
#include <AzCore/std/string/wildcard.h>
#endif

namespace AZ
{
    namespace IO
    {
        using namespace AzFramework::AssetSystem;
        //////////////////////////////////////////////////////////////////////////
        const char* const NetworkFileIOChannel = "NetworkFileIO";
#ifndef REMOTEFILEIO_IS_NETWORKFILEIO
        const char* const RemoteFileIOChannel = "RemoteFileIO";
        const char* const RemoteFileCacheChannel = "RemoteFileCache";
#endif

        const size_t READ_CHUNK_SIZE = 1024 * 256;

#ifdef NETWORKFILEIO_LOG
        AZ::OSString s_IOLog;
        AZ::u64 s_fileOperation = 0;

        class LogCall
        {
        public:
            LogCall(const char* name)
                :m_name(name)
            {
                m_fileOperation = s_fileOperation++;
                s_IOLog.append(AZStd::string::format("%u Start ", m_fileOperation));
                s_IOLog.append(m_name);
                s_IOLog.append("\r\n");
            }
            
            void Append(const char* line)
            {
                s_IOLog.append(AZStd::string::format("%u ", m_fileOperation));
                s_IOLog.append(line);
                s_IOLog.append("\r\n");
            }
            
            ~LogCall()
            {
                s_IOLog.append(AZStd::string::format("%u End ", m_fileOperation));
                s_IOLog.append(m_name);
                s_IOLog.append("\r\n");
            }

            AZStd::string m_name;
            AZ::u64 m_fileOperation = 0;
        };

        #define REMOTEFILE_LOG_CALL(x) LogCall lc(x)
        #define REMOTEFILE_LOG_APPEND(x) lc.Append(x)
#else
        #define REMOTEFILE_LOG_CALL(x) {}
        #define REMOTEFILE_LOG_APPEND(x) {}

#endif


        NetworkFileIO::NetworkFileIO()
        {
            REMOTEFILE_LOG_CALL("NetworkFileIO()::NetworkFileIO()");
        }

        NetworkFileIO::~NetworkFileIO()
        {
            REMOTEFILE_LOG_CALL("NetworkFileIO()::~NetworkFileIO()");
            AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFilesGuard);
            while (!m_remoteFiles.empty())
            {
                HandleType fileHandle = m_remoteFiles.begin()->first;
                Close(fileHandle);
            }
        }

        Result NetworkFileIO::Open(const char* filePath, OpenMode openMode, HandleType& fileHandle)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Open(filepath=%s, openMode=%i, fileHandle=OUT)",filePath?filePath:"nullptr", openMode).c_str());
            //error checks
            if (!filePath)
            {
                REMOTEFILE_LOG_APPEND("NetworkFileIO()::Open(filePath=nullptr) return Error");
                return ResultCode::Error;
            }

            if (openMode == OpenMode::Invalid)
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO()::Open(filepath=%s, openMode=%i, fileHandle=OUT) openMode=InvalidMode return Error", filePath, openMode).c_str());
                return ResultCode::Error;
            }

            //build a request
            uint32_t mode = static_cast<uint32_t>(openMode);
            FileOpenRequest request(filePath, mode);
            FileOpenResponse response;
            if (!SendRequest(request, response))
            {
                AZ_Assert(false, "NetworkFileIO()::Open(filepath=%s, openMode=%i, fileHandle=OUT) unable to send request. return Error", filePath, openMode);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO()::Open(filepath=%s, openMode=%i, fileHandle=OUT) unable to send request. return Error", filePath, openMode).c_str());
                return ResultCode::Error;
            }

            ResultCode returnValue = static_cast<ResultCode>(response.m_returnCode);
            if (returnValue == ResultCode::Success)
            {
                fileHandle = response.m_fileHandle;

                //track the open file handles
                AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFilesGuard);

                m_remoteFiles.insert(AZStd::make_pair(fileHandle, filePath));
            }

            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO()::Open(filepath=%s, openMode=%i, fileHandle=%u) return %s", filePath, openMode, fileHandle, returnValue == ResultCode::Success ? "Success" : "Error").c_str());
            return returnValue;
        }

        Result NetworkFileIO::Close(HandleType fileHandle)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Close(fileHandle=%u)", fileHandle).c_str());
            //build a request
            FileCloseRequest request(fileHandle);
            if (!SendRequest(request))
            {
                AZ_Assert(false, "NetworkFileIO()::Close(fileHandle=%u) Failed to send request. return Error", fileHandle);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO()::Close(fileHandle=%u) Failed to send request. return Error", fileHandle).c_str());
                return ResultCode::Error;
            }

            //clean up the handle and cache
            {
                AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFilesGuard);
                m_remoteFiles.erase(fileHandle);
            }

            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO()::Close(fileHandle=%u) return Success", fileHandle).c_str());
            return ResultCode::Success;
        }

        Result NetworkFileIO::Tell(HandleType fileHandle, AZ::u64& offset)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Tell(fileHandle=%u, offset=OUT)", fileHandle).c_str());
            FileTellRequest request(fileHandle);
            FileTellResponse responce;
            if(!SendRequest(request, responce))
            {
                AZ_Assert(false, "NetworkFileIO::Tell(fileHandle=%u) Failed to send tell request. return Error", fileHandle);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Tell(fileHandle=%u) Failed to send tell request. return Error", fileHandle).c_str());
                return ResultCode::Error;
            }

            ResultCode returnValue = static_cast<ResultCode>(responce.m_resultCode);
            if (returnValue == ResultCode::Error)
            {
                AZ_TracePrintf(NetworkFileIOChannel, "NetworkFileIO::Tell(fileHandle=%u) tell request failed", fileHandle);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Tell(fileHandle=%u) tell request failed", fileHandle).c_str());
                return ResultCode::Error;
            }

            offset = responce.m_offset;
            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Tell(fileHandle=%u) offset=%u return Success", fileHandle, offset).c_str());
            return ResultCode::Success;
        }

        Result NetworkFileIO::Seek(HandleType fileHandle, AZ::s64 offset, SeekType type)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Seek(fileHandle=%u, offset=%i, type=%s)", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown").c_str());
            FileSeekRequest request(fileHandle, static_cast<AZ::u32>(type), offset);
            if(!SendRequest(request))
            {
                AZ_Assert(false, "NetworkFileIO::Seek() Failed to send request, fileHandle=%u. return Error", fileHandle);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Seek(fileHandle=%u) Failed to send request. return Error", fileHandle).c_str());
                return ResultCode::Error;
            }
            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Seek(fileHandle=%u) return Success", fileHandle).c_str());
            return ResultCode::Success;
        }

        Result NetworkFileIO::Size(HandleType fileHandle, AZ::u64& size)
        {
            size = 0;
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Size(fileHandle=%u, size=OUT)", fileHandle).c_str());
            AZStd::string fileName;
            {
                AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFilesGuard);
                auto remoteIter = m_remoteFiles.find(fileHandle);
                if (remoteIter != m_remoteFiles.end())
                {
                    fileName = remoteIter->second.c_str();
                }
            }

            if (!fileName.empty())
            {
                return Size(fileName.c_str(), size);
            }
            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Size(fileHandle=%u) fileHandle not found! return Error", fileHandle).c_str());
            return ResultCode::Error;
        }

        Result NetworkFileIO::Size(const char* filePath, AZ::u64& size)
        {
            size = 0;
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Size(filePath=%s, size=OUT)", filePath?filePath:"nullptr").c_str());
            //error checks
            if (!filePath)
            {
                REMOTEFILE_LOG_APPEND("NetworkFileIO::Size(filePath=nullptr) return Error");
                return ResultCode::Error;
            }

            if (!strlen(filePath))
            {
                REMOTEFILE_LOG_APPEND("NetworkFileIO::Size(filePath=\"\") strlen(filePath)==0 return Error");
                return ResultCode::Error;
            }

            FileSizeRequest request(filePath);
            FileSizeResponse response;
            if (!SendRequest(request, response))
            {
                AZ_Assert(false, "NetworkFileIO::Size(filePath=%s) failed to send request. return Error", filePath);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Size(filePath=%s) failed to send request. return Error", filePath).c_str());
                return ResultCode::Error;
            }

            ResultCode returnValue = static_cast<ResultCode>(response.m_resultCode);
            if (returnValue == ResultCode::Error)
            {
                AZ_TracePrintf(NetworkFileIOChannel, "NetworkFileIO::Size(filePath=%s) size request failed. return Error", filePath);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Size(filePath=%s) size request failed. return Error", filePath).c_str());
                return ResultCode::Error;
            }
            
            size = response.m_size;
            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Size(filePath=%s) size=%u. return Success", filePath, size).c_str());
            return ResultCode::Success;
        }

        Result NetworkFileIO::Read(HandleType fileHandle, void* buffer, AZ::u64 size, bool failOnFewerThanSizeBytesRead, AZ::u64* bytesRead)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Read(filehandle=%i, buffer=OUT, size=%u, failOnFewerThanSizeBytesRead=%s, bytesRead=OUT)", fileHandle, size, failOnFewerThanSizeBytesRead ? "True" : "False").c_str());
            size_t remainingBytesToRead = size;
            AZ::u64 actualRead = 0;
            while(remainingBytesToRead)
            {
                AZ::u64 readSize = GetMin<AZ::u64>(remainingBytesToRead, READ_CHUNK_SIZE);
                FileReadRequest request(fileHandle, readSize, false);
                FileReadResponse response;
                if (!SendRequest(request, response))
                {
                    AZ_Assert(false, "NetworkFileIO::Read(filehandle=%i, size=%u) request failed. return Error", fileHandle, size);
                    REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Read(filehandle=%i, size=%u) request failed. return Error", fileHandle, size).c_str());
                    return ResultCode::Error;
                }

                //note the response could be ANY size, could be less so be careful
                AZ::u64 responseDataSize = response.m_data.size();
                if (responseDataSize <= remainingBytesToRead == false)
                {
                    AZ_TracePrintf(NetworkFileIOChannel, "NetworkFileIO::Read(filehandle=%i, size=%u) responseDataSize too large!!! responseDataSize=%u <= remainingBytesToRead=%u", fileHandle, size, responseDataSize, remainingBytesToRead);
                    REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Read(filehandle=%i, size=%u) responseDataSize too large!!! responseDataSize=%u <= remainingBytesToRead=%u", fileHandle, size, responseDataSize, remainingBytesToRead).c_str());
                }

                ResultCode returnValue = static_cast<ResultCode>(response.m_resultCode);

                //only copy as much as we can
                memcpy(buffer, response.m_data.data(), responseDataSize);
                buffer = reinterpret_cast<char*>(buffer) + responseDataSize;

                //only reduce by what should have come back
                remainingBytesToRead -= responseDataSize;

                //only record read bytes
                actualRead += responseDataSize;
                if(bytesRead)
                {
                    *bytesRead = actualRead;
                }

                //if we get an error, we only return an error if failOnFewerThanSizeBytesRead
                if(returnValue == ResultCode::Error)
                {
                    AZ_TracePrintf(NetworkFileIOChannel, "NetworkFileIO::Read: request failed, fileHandle=%u", fileHandle);
                    if(failOnFewerThanSizeBytesRead && remainingBytesToRead)
                    {
                        REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Read(fileHandle=%u, size=%u) actualRead=%u failed On Fewer Than Size Bytes Read. return Error", fileHandle, size, actualRead).c_str());
                        return ResultCode::Error;
                    }
                    REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Read(fileHandle=%u, size=%u) actualRead=%u return Success", fileHandle, size, actualRead).c_str());
                    return ResultCode::Success;
                }
                else if(!responseDataSize)
                {
                    break;
                }
            }

            if(failOnFewerThanSizeBytesRead && remainingBytesToRead)
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Read(fileHandle=%u, size=%u) actualRead=%u failed On Fewer Than Size Bytes Read. return Error", fileHandle, size, actualRead).c_str());
                return ResultCode::Error;
            }
            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Read(fileHandle=%u, size=%u) actualRead=%u return Success", fileHandle, size, actualRead).c_str());
            return ResultCode::Success;
        }

        Result NetworkFileIO::Write(HandleType fileHandle, const void* buffer, AZ::u64 size, AZ::u64* bytesWritten)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Write(fileHandle=%u, buffer=OUT, size=%u, bytesWritten=OUT)", fileHandle, size).c_str());
            FileWriteRequest request(fileHandle, buffer, size);
            //always async and just return success unless bytesWritten is set then synchronous
            if (bytesWritten)
            {
                FileWriteResponse response;
                if (!SendRequest(request, response))
                {
                    AZ_Assert(false, "NetworkFileIO::Write(fileHandle=%u, size=%u) failed to send sync write request. return Error", fileHandle, size);
                    REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Write(fileHandle=%u, size=%u) failed to send sync write request. return Error", fileHandle, size).c_str());
                    return ResultCode::Error;
                }

                if (static_cast<ResultCode>(response.m_resultCode) == ResultCode::Error)
                {
                    AZ_TracePrintf(NetworkFileIOChannel, "NetworkFileIO::Write(fileHandle=%u, size=%u) sync write request failed. return Error", fileHandle, size);
                    REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Write(fileHandle=%u, size=%u) sync write request failed. return Error", fileHandle, size).c_str());
                    return ResultCode::Error;
                }

                *bytesWritten = response.m_bytesWritten;
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Write(fileHandle=%u, size=%u) actualWrite=%u return Success", fileHandle, size, response.m_bytesWritten).c_str());
                return ResultCode::Success;
            }
            else
            {
                // just send the message and assume we wrote it all successfully.
                if (!SendRequest(request))
                {
                    AZ_Assert(false, "NetworkFileIO::Write(fileHandle=%u, size=%u) failed to send async write request. return Error", fileHandle, size);
                    REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Write(fileHandle=%u, size=%u) failed to send async write request. return Error", fileHandle, size).c_str());
                    return ResultCode::Error;
                }
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Write(fileHandle=%u, size=%u) return Success", fileHandle).c_str());
                return ResultCode::Success;
            }
        }

        Result NetworkFileIO::Flush(HandleType fileHandle)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Flush(fileHandle=%u)", fileHandle).c_str());
            // just send the message, no need to wait for flush response.
            FileFlushRequest request(fileHandle);
            if (!SendRequest(request))
            {
                AZ_Assert(false, "NetworkFileIO::Flush(fileHandle=%u) failed to send request. return Error", fileHandle);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Flush(fileHandle=%u) failed to send request. return Error", fileHandle).c_str());
                return ResultCode::Error;
            }

            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Flush(fileHandle=%u) return Success", fileHandle).c_str());
            return ResultCode::Success;
        }

        bool NetworkFileIO::Eof(HandleType fileHandle)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Eof(fileHandle=%u)", fileHandle).c_str());
            AZ::u64 sizeValue = 0;
            Result res = Size(fileHandle, sizeValue);
            AZ::u64 offset = 0;
            res = Tell(fileHandle, offset);

            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Eof(fileHandle=%u) return %s", fileHandle, offset >= sizeValue ? "True" : "False").c_str());
            return offset >= sizeValue;
        }

        bool NetworkFileIO::Exists(const char* filePath)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Exists(filePath=%s)", filePath?filePath:"nullptr").c_str());
            //error checks
            if (!filePath)
            {
                REMOTEFILE_LOG_APPEND("NetworkFileIO::Exists(filePath=nullptr) return False");
                return false;
            }

            if (!strlen(filePath))
            {
                REMOTEFILE_LOG_APPEND("NetworkFileIO::Exists(filePath=\"\") strlen(filePath)==0. return False");
                return false;
            }

            FileExistsRequest request(filePath);
            FileExistsResponse response;
            if (!SendRequest(request, response))
            {
                AZ_Assert(false, "NetworkFileIO::Exists(filePath=%s) failed to send request. return False", filePath);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Exists(filePath=%s) failed to send request. return False", filePath).c_str());
                return false;
            }

            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Exists(filePath=%s) return %s", filePath, response.m_exists ? "True" : "False").c_str());
            return response.m_exists;
        }

        AZ::u64 NetworkFileIO::ModificationTime(const char* filePath)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::ModificationTime(filePath=%s)", filePath?filePath:"nullptr").c_str());
            //error checks
            if (!filePath)
            {
                REMOTEFILE_LOG_APPEND("NetworkFileIO::ModificationTime(filePath=nullptr) return 0");
                return 0;
            }

            if (!strlen(filePath))
            {
                REMOTEFILE_LOG_APPEND("NetworkFileIO::ModificationTime(filePath=\"\") strlen(filePath)==0. return 0");
                return 0;
            }

            FileModTimeRequest request(filePath);
            FileModTimeResponse response;
            if (!SendRequest(request, response))
            {
                AZ_Assert(false, "NetworkFileIO::ModificationTime(filePath=%s) failed to send request. return 0", filePath);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::ModificationTime(filePath=%s) failed to send request. return 0", filePath).c_str());
                return 0;
            }
            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::ModificationTime(filePath=%s) return %i", filePath, response.m_modTime).c_str());
            return response.m_modTime;
        }

        AZ::u64 NetworkFileIO::ModificationTime(HandleType fileHandle)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::ModificationTime(fileHandle=%u)", fileHandle).c_str());
            {
                AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFilesGuard);
                auto remoteIter = m_remoteFiles.find(fileHandle);
                if (remoteIter != m_remoteFiles.end())
                {
                    return ModificationTime(remoteIter->second.c_str());
                }
            }
            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::ModificationTime(fileHandle=%u) return 0!", fileHandle).c_str());
            return 0;
        }

        bool NetworkFileIO::IsDirectory(const char* filePath)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::IsDirectory(filePath=%s)", filePath?filePath:"nullptr").c_str());
            //error checks
            if (!filePath)
            {
                REMOTEFILE_LOG_APPEND("NetworkFileIO::IsDirectory(filePath=nullptr) filePath=nullptr. return False");
                return false;
            }

            if (!strlen(filePath))
            {
                REMOTEFILE_LOG_APPEND("NetworkFileIO::IsDirectory(filePath=\"\") strlen(filePath)=0. return False");
                return false;
            }

            PathIsDirectoryRequest request(filePath);
            PathIsDirectoryResponse response;
            if (!SendRequest(request, response))
            {
                AZ_Assert(false, "NetworkFileIO::IsDirectory(filePath=%s) failed to send request. return False", filePath);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::IsDirectory(filePath=%s) failed to send request. return False", filePath).c_str());
                return false;
            }
            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::IsDirectory(filePath=%s) return %s", filePath, response.m_isDir ? "True": "False").c_str());
            return response.m_isDir;
        }

        bool NetworkFileIO::IsReadOnly(const char* filePath)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::IsReadOnly(filePath=%s)", filePath?filePath:"nullptr").c_str());
            //error checks
            if (!filePath)
            {
                REMOTEFILE_LOG_APPEND("NetworkFileIO::IsReadOnly(filePath=nullptr) return False");
                return false;
            }

            if (!strlen(filePath))
            {
                REMOTEFILE_LOG_APPEND("NetworkFileIO::IsReadOnly(filePath=\"\") strlen(filePath)==0. return False");
                return false;
            }

            FileIsReadOnlyRequest request(filePath);
            FileIsReadOnlyResponse response;
            if (!SendRequest(request, response))
            {
                AZ_Assert(false, "NetworkFileIO::IsReadOnly(filePath=%s) failed to send request. return False", filePath);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::IsReadOnly(filePath=%s) failed to send request. return False", filePath).c_str());
                return false;
            }

            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::IsReadOnly(filePath=%s) return %s", filePath, response.m_isReadOnly ? "True" : "False").c_str());
            return response.m_isReadOnly;
        }

        Result NetworkFileIO::CreatePath(const char* filePath)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::CreatePath(filePath=%s)", filePath?filePath:"nullptr").c_str());
            //error checks
            if (!filePath)
            {
                REMOTEFILE_LOG_APPEND("NetworkFileIO::CreatePath(filePath=nullptr) filePath=nullptr. return Error");
                return ResultCode::Error;
            }

            if (!strlen(filePath))
            {
                REMOTEFILE_LOG_APPEND("NetworkFileIO::CreatePath(filePath=\"\") strlen(filePath)==0. return Error");
                return ResultCode::Error;
            }

            PathCreateRequest request(filePath);
            PathCreateResponse response;
            if (!SendRequest(request, response))
            {
                AZ_Assert(false, "NetworkFileIO::CreatePath(filePath=%s) failed to send request. return Error", filePath);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::CreatePath(filePath=%s) failed to send request. return Error", filePath).c_str());
                return ResultCode::Error;
            }

            ResultCode returnValue = static_cast<ResultCode>(response.m_resultCode);
            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::CreatePath(filePath=%s) return %s", filePath, returnValue == ResultCode::Success ? "Success" : "Error").c_str());
            return returnValue;
        }

        Result NetworkFileIO::DestroyPath(const char* filePath)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::DestroyPath(filePath=%s)", filePath?filePath:"nullptr").c_str());
            //error checks
            if (!filePath)
            {
                REMOTEFILE_LOG_APPEND("NetworkFileIO::DestroyPath(filePath=nullptr) filePth=nullptr. return Error");
                return ResultCode::Error;
            }

            if (!strlen(filePath))
            {
                REMOTEFILE_LOG_APPEND("NetworkFileIO::DestroyPath(filePath=\"\") strlen(filePath)==0. return Error");
                return ResultCode::Error;
            }

            PathDestroyRequest request(filePath);
            PathDestroyResponse response;
            if (!SendRequest(request, response))
            {
                AZ_Assert(false, "NetworkFileIO::DestroyPath(filePath=%s) failed to send request. return Error", filePath);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::DestroyPath(filePath=%s) failed to send request. return Error", filePath).c_str());
                return ResultCode::Error;
            }

            ResultCode returnValue = static_cast<ResultCode>(response.m_resultCode);
            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::DestroyPath(filePath=%s) return %s", filePath, returnValue == ResultCode::Success ? "Success" : "Error").c_str());
            return returnValue;
        }

        Result NetworkFileIO::Remove(const char* filePath)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Remove(filePath=%s)", filePath?filePath:"nullptr").c_str());
            //error checks
            if (!filePath)
            {
                REMOTEFILE_LOG_APPEND("NetworkFileIO::Remove(filePath=nullptr) filePth=nullptr. return Error");
                return ResultCode::Error;
            }

            if (!strlen(filePath))
            {
                REMOTEFILE_LOG_APPEND("NetworkFileIO::Remove(filePath=\"\") strlen(filePath)==0. return Error");
                return ResultCode::Error;
            }

            FileRemoveRequest request(filePath);
            FileRemoveResponse response;
            if (!SendRequest(request, response))
            {
                AZ_Assert(false, "NetworkFileIO::Remove(filePath=%s) failed to send request. return Error", filePath);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Remove(filePath=%s) failed to send request. return Error", filePath).c_str());
                return ResultCode::Error;
            }

            ResultCode returnValue = static_cast<ResultCode>(response.m_resultCode);
            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Remove(filePath=%s) return %s", filePath, returnValue == ResultCode::Success ? "Success" : "Error").c_str());
            return returnValue;
        }

        Result NetworkFileIO::Copy(const char* sourceFilePath, const char* destinationFilePath)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Copy(sourceFilePath=%s, destinationFilePath=%s)", sourceFilePath?sourceFilePath:"nullptr", destinationFilePath?destinationFilePath:"nullptr").c_str());
            //error checks
            if (!sourceFilePath)
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Copy(sourceFilePath=nullptr, destinationFilePath=%s) return Error", destinationFilePath?destinationFilePath:"nullptr").c_str());
                return ResultCode::Error;
            }

            if (!destinationFilePath)
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Copy(sourceFilePath=%s, destinationFilePath=nullptr) return Error", sourceFilePath?sourceFilePath:"nullptr").c_str());
                return ResultCode::Error;
            }

            if (!strlen(sourceFilePath))
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Copy(sourceFilePath=\"\", destinationFilePath=%s) strlen(sourceFilePath)==0. return Error", destinationFilePath?destinationFilePath:"nullptr").c_str());
                return ResultCode::Error;
            }

            //fail if the source doesn't exist
            if (!Exists(sourceFilePath))
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Copy(sourceFilePath=%s, destinationFilePath=%s) Exists(sourceFilePath=%s)==False. return Error", sourceFilePath, destinationFilePath, sourceFilePath).c_str());
                return ResultCode::Error;
            }

            bool bSourceExcluded = false;
            bool bDestinationExcluded = false;

            //else both are remote so just issue the remote copy command
            FileCopyRequest request(sourceFilePath, destinationFilePath);
            FileCopyResponse response;
            if (!SendRequest(request, response))
            {
                AZ_Assert(false, "NetworkFileIO::Copy(sourceFilePath=%s, destinationFilePath=%s) failed to send request. return Error", sourceFilePath, destinationFilePath);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Copy() failed to send request for %s -> %s. return Error", sourceFilePath, destinationFilePath).c_str());
                return ResultCode::Error;
            }

            ResultCode returnValue = static_cast<ResultCode>(response.m_resultCode);
            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Copy(sourceFilePath=%s, destinationFilePath=%s) return %s", sourceFilePath, destinationFilePath, returnValue == ResultCode::Success ? "Success" : "Error").c_str());
            return returnValue;
        }

        Result NetworkFileIO::Rename(const char* sourceFilePath, const char* destinationFilePath)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Rename(sourceFilePath=%s, destinationFilePath=%s)", sourceFilePath?sourceFilePath:"nullptr", destinationFilePath?destinationFilePath:"nullptr").c_str());
            //error checks
            if (!sourceFilePath)
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Rename(sourceFilePath=nullptr, destinationFilePath=%s) sourceFilePath=nullptr. return Error", destinationFilePath?destinationFilePath:"nullptr").c_str());
                return ResultCode::Error;
            }

            if (!destinationFilePath)
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Rename(sourceFilePath=%s, destinationFilePath=nullptr) destinationFilePath=nullptr. return Error", sourceFilePath).c_str());
                return ResultCode::Error;
            }

            if (!strlen(sourceFilePath))
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Rename(sourceFilePath=\"\", destinationFilePath=%s) strlen(sourceFilePath)==0. return Error", destinationFilePath).c_str());
                return ResultCode::Error;
            }

            //fail if the source doesn't exist
            if (!Exists(sourceFilePath))
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Rename(sourceFilePath=%s, destinationFilePath=%s) Exists(sourceFilePath)=False. return Error", sourceFilePath, destinationFilePath).c_str());
                return ResultCode::Error;
            }

            if (!strlen(destinationFilePath))
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Rename(sourceFilePath=%s, destinationFilePath=\"\") strlen(destinationFilePath)==0. return Error", sourceFilePath).c_str());
                return ResultCode::Error;
            }

            //we are going to access shared memory so lock and copy the results into our memory
            bool bSourceExcluded = false;
            bool bDestinationExcluded = false;

            //if the source and destination are the same, shortcut
            if (!strcmp(sourceFilePath, destinationFilePath))
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Rename(sourceFilePath=%s, destinationFilePath=%s) sourceFilePath=destinationFilePath. return Error", sourceFilePath, destinationFilePath).c_str());
                return ResultCode::Error;
            }

            //if the destination exists
            if (Exists(destinationFilePath))
            {
                //if its read only fail
                if (IsReadOnly(destinationFilePath))
                {
                    REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Rename(sourceFilePath=%s, destinationFilePath=%s) IsReadOnly(destinationFilePath)=True. return Error", sourceFilePath, destinationFilePath).c_str());
                    return ResultCode::Error;
                }
            }

            //else both are remote so just issue the remote command
            FileRenameRequest request(sourceFilePath, destinationFilePath);
            FileRenameResponse response;
            if (!SendRequest(request, response))
            {
                AZ_Assert(false, "NetworkFileIO::Rename(sourceFilePath=%s, destinationFilePath=%s) failed to send request. return Error", sourceFilePath, destinationFilePath);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Rename(sourceFilePath=%s, destinationFilePath=%s) failed to send request. return Error", sourceFilePath, destinationFilePath).c_str());
                return ResultCode::Error;
            }

            ResultCode returnValue = static_cast<ResultCode>(response.m_resultCode);
            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Rename(sourceFilePath=%s, destinationFilePath=%s) return %s", sourceFilePath, destinationFilePath, returnValue == ResultCode::Success ? "Success" : "Error").c_str());
            return returnValue;
        }

        Result NetworkFileIO::FindFiles(const char* filePath, const char* filter, FindFilesCallbackType callback)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::FindFiles(filePath=%s, filter=%s, callback=OUT)", filePath?filePath:"nullptr", filter?filter:"nullptr").c_str());
            //error checks
            if (!filePath)
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::FindFiles(filePath=nullptr, filter=%s) filePath=nullptr. return Error", filter?filter:"nulltpr").c_str());
                return ResultCode::Error;
            }

            if (!filter)
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::FindFiles(filePath=%s, filter=nullptr) filter=nullptr. return Error", filePath).c_str());
                return ResultCode::Error;
            }

            if (!strlen(filePath))
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::FindFiles(filePath=\"\", filter=%s) strlen(filePath)==0. return Error", filter).c_str());
                return ResultCode::Error;
            }

            FindFilesRequest request(filePath, filter);
            FindFilesResponse response;
            if (!SendRequest(request, response))
            {
                AZ_Assert(false, "NetworkFileIO::FindFiles(filePath=%s, filter=%s) could not send request. return Error", filePath, filter);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::FindFiles(filePath=%s, filter=%s) could not send request. return Error", filePath, filter).c_str());
                return ResultCode::Error;
            }

            ResultCode returnValue = static_cast<ResultCode>(response.m_resultCode);
            if (returnValue == ResultCode::Success)
            {
                // callbacks
                const uint64_t numFiles = response.m_files.size();
                for (uint64_t fileIdx = 0; fileIdx < numFiles; ++fileIdx)
                {
                    const char* fileName = response.m_files[fileIdx].c_str();
                    if (!callback(fileName))
                    {
                        fileIdx = numFiles;//we are done
                    }
                }
            }
            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::FindFiles(filePath=%s, filter=%s) return %s", filePath, filter, returnValue == ResultCode::Success ? "Success" : "Error").c_str());
            return returnValue;
        }

        void NetworkFileIO::SetAlias(const char* alias, const char* path)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::SetAlias(alias=%s, path=%s)", alias?alias:"nullptr", path?path:"nullptr").c_str());
        }

        const char* NetworkFileIO::GetAlias(const char* alias)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::GetAlias(alias=%s)", alias?alias:"nullptr").c_str());
            REMOTEFILE_LOG_APPEND("NetworkFileIO::GetAlias() return nullptr");
            return nullptr;
        }

        void NetworkFileIO::ClearAlias(const char* alias)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::ClearAlias(alias=%s)", alias?alias:"nullptr").c_str());
        }

        AZ::u64 NetworkFileIO::ConvertToAlias(char* inOutBuffer, AZ::u64 bufferLength) const
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::ConvertToAlias(inOutBuffer=%s, bufferLength=%u)", inOutBuffer?inOutBuffer:"nullptr", bufferLength).c_str());
            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::ConvertToAlias() return %u", strlen(inOutBuffer)).c_str());
            return strlen(inOutBuffer);
        }

        bool NetworkFileIO::ResolvePath(const char* path, char* resolvedPath, AZ::u64 resolvedPathSize)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::ResolvePath(path=%s, resolvedPath=%s, resolvedPathsize=%u)", path?path:"nullptr", resolvedPath?resolvedPath:"nullptr", resolvedPathSize).c_str());
            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::ResolvePath(path=%s, resolvedPath=%s, resolvedPathsize=%u) return False", path?path:"nullptr", resolvedPath?resolvedPath:"nullptr", resolvedPathSize).c_str());
            return false;
        }

        bool NetworkFileIO::GetFilename(HandleType fileHandle, char* filename, AZ::u64 filenameSize) const
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::GetFilename(fileHandle=%u, filename=%s, filenamesize=%u)", fileHandle, filename?filename:"nullptr", filenameSize).c_str());
            {
                AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFilesGuard);
                const auto fileIt = m_remoteFiles.find(fileHandle);
                if (fileIt != m_remoteFiles.end())
                {
                    if (filenameSize >= fileIt->second.length())
                    {
                        azstrncpy(filename, filenameSize, fileIt->second.c_str(), fileIt->second.length());
                        REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO()::GetFilename(fileHandle=%u, filename=%s, filenamesize=%u) return True", fileHandle, filename?filename:"nullptr", filenameSize).c_str());
                        return true;
                    }
                    else
                    {
                        AZ_TracePrintf(NetworkFileIOChannel, "NetworkFileIO::GetFilename(fileHandle=%u, filename=%s, filenamesize=%u) Result buffer is too small %u", fileHandle, filename?filename:"nullptr", filenameSize, fileIt->second.length());
                        REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::GetFilename(fileHandle=%u, filename=%s, filenamesize=%u) Result buffer is too small %u", fileHandle, filename?filename:"nullptr", filenameSize, fileIt->second.length()).c_str());
                    }
                }
            }
            REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO()::GetFilename(fileHandle=%u, filename=%s, filenamesize=%u) return False", fileHandle, filename?filename:"nullptr", filenameSize).c_str());
            return false;
        }

        bool NetworkFileIO::IsRemoteIOEnabled()
        {
            REMOTEFILE_LOG_CALL("NetworkFileIO()::IsRemoteIOEnabled()");
            REMOTEFILE_LOG_APPEND("NetworkFileIO()::IsRemoteIOEnabled() return True");
            return true;
        }

#ifndef REMOTEFILEIO_IS_NETWORKFILEIO
        //////////////////////////////////////////////////////////////////////////
        const int REFRESH_FILESIZE_TIME = 500;// ms
        const size_t CACHE_LOOKAHEAD_SIZE = 1024 * 256;

        RemoteFileCache::RemoteFileCache(RemoteFileCache&& other)
        {
            REMOTEFILE_LOG_CALL("RemoteFileCache()::RemoteFileCache(other)");
            *this = AZStd::move(other);
        }

        AZ::IO::RemoteFileCache& RemoteFileCache::operator=(RemoteFileCache&& other)
        {
            REMOTEFILE_LOG_CALL("RemoteFileCache()::operator=(other)");
            if (this != &other)
            {
                m_cacheLookaheadBuffer = AZStd::move(other.m_cacheLookaheadBuffer);
                m_cacheLookaheadPos = other.m_cacheLookaheadPos;
                m_fileSize = other.m_fileSize;
                m_fileSizeTime = other.m_fileSizeTime;
                m_filePosition = other.m_filePosition;
                m_fileHandle = other.m_fileHandle;
                m_openMode = other.m_openMode;
            }
            return *this;
        }

        void RemoteFileCache::Invalidate()
        {
            m_cacheLookaheadPos = 0;
            m_cacheLookaheadBuffer.clear();
        }

        AZ::u64 RemoteFileCache::RemainingBytes()
        {
            return m_cacheLookaheadBuffer.size() - m_cacheLookaheadPos;
        }

        AZ::u64 RemoteFileCache::CacheFilePosition()
        {
            return m_filePosition - RemainingBytes();
        }

        AZ::u64 RemoteFileCache::CacheStartFilePosition()
        {
            return m_filePosition - m_cacheLookaheadBuffer.size();
        }

        AZ::u64 RemoteFileCache::CacheEndFilePosition()
        {
            return m_filePosition;
        }

        bool RemoteFileCache::IsFilePositionInCache(AZ::u64 filePosition)
        {
            return filePosition >= CacheStartFilePosition() && filePosition < CacheEndFilePosition();
        }

        void RemoteFileCache::SetCachePositionFromFilePosition(AZ::u64 filePosition)
        {
            m_cacheLookaheadPos = filePosition - CacheStartFilePosition();
        }
        
        void RemoteFileCache::SyncCheck()
        {
#ifdef REMOTEFILEIO_SYNC_CHECK
            //don't sync check files open for write
            //they can be written to asynchronously so tell may return a different position than is cached
            //because we dont wait for the network request to finish before we immediately ask for tell
            if (AnyFlag(m_openMode & OpenMode::ModeWrite) ||
                AnyFlag(m_openMode & OpenMode::ModeAppend) ||
                AnyFlag(m_openMode & OpenMode::ModeUpdate))
            {
                return;
            }
            REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileCache()::SyncCheck(m_fileHandle=%u)", m_fileHandle).c_str());
            FileTellRequest request(m_fileHandle);
            FileTellResponse responce;
            if (!SendRequest(request, responce))
            {
                AZ_Assert(false, "RemoteFileCache::SyncCheck(m_fileHandle=%u) Failed to send tell request.", m_fileHandle);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileCache::SyncCheck(m_fileHandle=%u) Failed to send tell request.", m_fileHandle).c_str());
            }

            ResultCode returnValue = static_cast<ResultCode>(responce.m_resultCode);
            if (returnValue == ResultCode::Error)
            {
                AZ_TracePrintf(RemoteFileCacheChannel, "RemoteFileCache::SyncCheck(m_fileHandle=%u) tell request failed.", m_fileHandle);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileCache::SyncCheck(m_fileHandle=%u) tell request failed.", m_fileHandle).c_str());
            }
            
            if (responce.m_offset != m_filePosition)
            {
                AZ_TracePrintf(RemoteFileCacheChannel, "RemoteFileCache::SyncCheck(m_fileHandle=%u) failed!!! m_filePosition=%u tell=%u", m_fileHandle, m_filePosition, responce.m_offset);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileCache::SyncCheck(m_fileHandle=%u) failed!!! m_filePosition=%u tell=%u", m_fileHandle, m_filePosition, responce.m_offset).c_str());
            }
            else
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileCache::SyncCheck(m_fileHandle=%u) Success m_filePosition=%u tell=%u", m_fileHandle, m_filePosition, responce.m_offset).c_str());
            }
#endif
        }


        void RemoteFileCache::SetFilePosition(AZ::u64 filePosition)
        {
            m_filePosition = filePosition;
            SyncCheck();
        }

        void RemoteFileCache::OffsetFilePosition(AZ::s64 offset)
        {
            m_filePosition += offset;
            SyncCheck();
        }

        bool RemoteFileCache::Eof()
        {
            return CacheFilePosition() == m_fileSize;
        }


        //////////////////////////////////////////////////////////////////////////
        RemoteFileIO::RemoteFileIO(FileIOBase* excludedFileIO)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::RemoteFileIO()").c_str());
            m_excludedFileIO = excludedFileIO;
#ifdef REMOTEFILEIO_CACHE_FILETREE
            CacheFileTree();
#endif
        }

        RemoteFileIO::~RemoteFileIO()
        {
            delete m_excludedFileIO; //for now delete it, when We change to always create local file io We won't
#ifdef REMOTEFILEIO_CACHE_FILETREE
            AzFramework::AssetCatalogEventBus::Handler::BusDisconnect();
#endif
            REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::RemoteFileIO()").c_str());
        }

        Result RemoteFileIO::Open(const char* filePath, OpenMode openMode, HandleType& fileHandle)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::Open(filePath=%s, openMode=%i, fileHandle=OUT)", filePath?filePath:"nullptr", openMode).c_str());
            Result returnValue = NetworkFileIO::Open(filePath, openMode, fileHandle);
            if (returnValue == ResultCode::Success)
            {
                {
                    AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard);
                    m_remoteFileCache.insert(fileHandle);
                    RemoteFileCache& cache = GetCache(fileHandle);
                    cache.m_fileHandle = fileHandle;
                    cache.m_openMode = openMode;
                }
            }
            REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Open(filePath=%s, fileHandle=%u) return %s", filePath?filePath:"nullptr", fileHandle, returnValue == ResultCode::Success ? "Success" : "Error").c_str());
            return returnValue;
        }

        Result RemoteFileIO::Close(HandleType fileHandle)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::Close(fileHandle=%u)", fileHandle).c_str());
            Result returnValue = NetworkFileIO::Close(fileHandle);
            
            if (returnValue == ResultCode::Success)
            {
                AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard);
                m_remoteFileCache.erase(fileHandle);
            }
            REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Open(fileHandle=%u) return %s", fileHandle, returnValue == ResultCode::Success ? "Success" : "Error").c_str());
            return returnValue;
        }

        Result RemoteFileIO::Tell(HandleType fileHandle, AZ::u64& offset)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::Tell(fileHandle=%u, offset=OUT)", fileHandle).c_str());
            {
                AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard);
                RemoteFileCache& cache = GetCache(fileHandle);
                offset = cache.CacheFilePosition();
            }
            REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Tell(fileHandle=%u, offset=%i) return Success", fileHandle, offset).c_str());
            return ResultCode::Success;
        }

        Result RemoteFileIO::Seek(HandleType fileHandle, AZ::s64 offset, SeekType type)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::Seek(fileHandle=%u, offset=%i, type=%s)", fileHandle, offset, type==SeekType::SeekFromCurrent ? "SeekFromCurrent" : type==SeekType::SeekFromEnd ? "SeekFromEnd" : type==SeekType::SeekFromStart ? "SeekFromStart" : "Unknown").c_str());
            AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard);
            RemoteFileCache& cache = GetCache(fileHandle);

            //if we land in the cache all we need to do is adjust the cache position and return
            //calculate the new position in the file
            AZ::s64 newFilePosition = 0;
            if (type == AZ::IO::SeekType::SeekFromCurrent)
            {
                newFilePosition = cache.CacheFilePosition() + offset;
            }
            else if (type == AZ::IO::SeekType::SeekFromStart)
            {
                newFilePosition = offset;
            }
            else if (type == AZ::IO::SeekType::SeekFromEnd)
            {
                AZ::u64 fileSize = 0;
                if(Size(fileHandle, fileSize)== ResultCode::Error)
                {
                    return ResultCode::Error;
                }
                newFilePosition = fileSize + offset;
            }
            else
            {
                AZ_Assert(false, "RemoteFileIO::Seek(fileHandle=%u, offset=%i, type=%s) unknown seektype. return Error", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown");
                REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Seek(fileHandle=%u, offset=%i, type=%s) unknown seektype. return Error", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown").c_str());
                return ResultCode::Error;
            }

            //bound check
            //note that seeking beyond end or before beginning is system dependent
            //therefore we will define that on all platforms it is not allowed
            if (newFilePosition < 0)
            {
                AZ_TracePrintf(RemoteFileIOChannel, "RemoteFileIO::Seek(fileHandle=%u, offset=%i, type=%s) seek to a position before the begining of a file!", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown");
                REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Seek(fileHandle=%u, offset=%i, type=%s) seek to a position before the begining of a file!", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown").c_str());
                newFilePosition = 0;
            }
            else
            {
                AZ::u64 fileSize = 0;
                Size(fileHandle, fileSize);

                if (newFilePosition > fileSize)
                {
                    AZ_TracePrintf(RemoteFileIOChannel, "RemoteFileIO::Seek(fileHandle=%u, offset=%i, type=%s) seek to a position after the end of a file!", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown");
                    REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Seek(fileHandle=%u, offset=%i, type=%s) seek to a position after the end of a file!", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown").c_str());
                    newFilePosition = fileSize;
                }
            }

            //see if the new calculated position is in the cache
            if (cache.IsFilePositionInCache(newFilePosition))
            {
                //it is, so calculate what the cache position should be given the new file position and
                //set it and return success
                cache.SetCachePositionFromFilePosition(newFilePosition);
                cache.SyncCheck();
                return ResultCode::Success;
            }
            else if (newFilePosition == cache.m_filePosition)
            {
                cache.SyncCheck();
                return ResultCode::Success;
            }

            //we didn't land in the cache
            //perform the seek for real, invalidate and set new file position
            //note when setting a new absolute position we always use SeekFromStart, not the passed in seek type
            FileSeekRequest request(fileHandle, static_cast<AZ::u32>(AZ::IO::SeekType::SeekFromStart), newFilePosition);
            if (!SendRequest(request))
            {
                AZ_Assert(false, "RemoteFileIO::Seek(fileHandle=%u, offset=%i, type=%s) Failed to send request. return Error", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown");
                REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Seek(fileHandle=%u, offset=%i, type=%s) Failed to send request. return Error", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown").c_str());
                return ResultCode::Error;
            }

            cache.Invalidate();
            cache.SetFilePosition(newFilePosition);
            REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Seek(fileHandle=%u, offset=%i, type=%s) return Success", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown").c_str());
            return ResultCode::Success;
        }

        Result RemoteFileIO::Size(HandleType fileHandle, AZ::u64& size)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO::Size(fileHandle=%u, size=OUT)", fileHandle).c_str());
            AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard);
            RemoteFileCache& cache = GetCache(fileHandle);

            // do we even have to check?
            AZ::u64 msNow = AZStd::GetTimeUTCMilliSecond();
            if (msNow - cache.m_fileSizeTime > REFRESH_FILESIZE_TIME)
            {
                if (NetworkFileIO::Size(fileHandle, cache.m_fileSize))
                {
                    cache.m_fileSizeTime = msNow;
                    size = cache.m_fileSize;
                    REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Size(fileHandle=%u) size=%u. return Success", fileHandle, size).c_str());
                    return ResultCode::Success;
                }
            }
            else
            {
                size = cache.m_fileSize;
                REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Size(fileHandle=%u) size=%u return Success", fileHandle, size).c_str());
                return ResultCode::Success;
            }
            REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Size(fileHandle=%u) return Error", fileHandle).c_str());
            return ResultCode::Error;
        }

        Result RemoteFileIO::Read(HandleType fileHandle, void* buffer, AZ::u64 size, bool failOnFewerThanSizeBytesRead, AZ::u64* bytesRead)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::Read(fileHandle=%u, buffer=OUT, size=%u, failOnFewerThanSizeBytesRead=%s, bytesRead=OUT)", fileHandle, size, failOnFewerThanSizeBytesRead ? "True" : "False").c_str());
            AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard);
            RemoteFileCache& cache = GetCache(fileHandle);
            
            AZ::u64 remainingBytesToRead = size;
            AZ::u64 bytesReadFromCache = 0;
            AZ::u64 remainingBytesInCache = cache.RemainingBytes();
            if (remainingBytesInCache)
            {
                AZ::u64 bytesToReadFromCache = AZStd::GetMin<AZ::u64>(remainingBytesInCache, remainingBytesToRead);
                memcpy(buffer, cache.m_cacheLookaheadBuffer.data() + cache.m_cacheLookaheadPos, bytesToReadFromCache);
                bytesReadFromCache = bytesToReadFromCache;
                remainingBytesToRead -= bytesToReadFromCache;
                cache.m_cacheLookaheadPos += bytesToReadFromCache;
            }

            REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Read(fileHandle=%u) bytesReadFromCache=%u remainingBytesToRead=%u", fileHandle, bytesReadFromCache, remainingBytesToRead).c_str());
            AZ::u64 bytesThatHaveBeenRead = bytesReadFromCache;
            if (bytesRead)
            {
                *bytesRead = bytesThatHaveBeenRead;
            }

            buffer = reinterpret_cast<char*>(buffer) + bytesReadFromCache;

            if (remainingBytesToRead)
            {
                AZ::u64 actualRead = 0;
                Result returnValue = NetworkFileIO::Read(fileHandle, buffer, remainingBytesToRead, true, &actualRead);

                remainingBytesToRead -= actualRead;

                if (actualRead)
                {
                    cache.OffsetFilePosition(actualRead);
                }

                bytesThatHaveBeenRead += actualRead;
                if (bytesRead)
                {
                    *bytesRead = bytesThatHaveBeenRead;
                }

                REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Read(fileHandle=%u, size=%u) %s remainingBytesToRead=%u. actualRead=%u", fileHandle, remainingBytesToRead, returnValue==ResultCode::Success ? "Success" : "Fail", remainingBytesToRead, actualRead).c_str());
                //if we get an error, we only return an error if failOnFewerThanSizeBytesRead
                if (returnValue == ResultCode::Error)
                {
                    if (failOnFewerThanSizeBytesRead && remainingBytesToRead)
                    {
                        REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Read(fileHandle=%u, bytesThatHaveBeenRead=%u) failOnFewerThanSizeBytesRead. return Error", fileHandle, bytesThatHaveBeenRead).c_str());
                        return ResultCode::Error;
                    }
                    REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Read(fileHandle=%u, bytesThatHaveBeenRead=%u) return Success", fileHandle, bytesThatHaveBeenRead).c_str());
                    return ResultCode::Success;
                }
            }

            //they could have asked for more bytes than there is in the file
            if (failOnFewerThanSizeBytesRead && remainingBytesToRead && Eof(fileHandle))
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Read(fileHandle=%u, bytesThatHaveBeenRead=%u) failOnFewerThanSizeBytesRead. return Error", fileHandle, bytesThatHaveBeenRead).c_str());
                return ResultCode::Error;
            }

            //if we get here we have satisfied the read request.
            //if the cache is empty try to refill it if not eof.
            if (!cache.RemainingBytes() && !Eof(fileHandle))
            {
                //make sure the cache file size is up to date
                AZ::u64 fsize = 0;
                Size(fileHandle, fsize);

                AZ::u64 remainingFileBytes = cache.m_fileSize - cache.m_filePosition;
                AZ::u64 readSize = AZStd::GetMin<AZ::u64>(remainingFileBytes, CACHE_LOOKAHEAD_SIZE);

                cache.m_cacheLookaheadBuffer.clear();
                cache.m_cacheLookaheadBuffer.resize_no_construct(readSize);
                REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Read(fileHandle=%u, size=%u) -=CACHE READ=-", fileHandle, readSize).c_str());
                AZ::u64 actualRead = 0;
                Result returnValue = NetworkFileIO::Read(fileHandle, cache.m_cacheLookaheadBuffer.data(), readSize, false, &actualRead);
                if (actualRead)
                {
                    cache.m_cacheLookaheadBuffer.resize(actualRead);
                    cache.OffsetFilePosition(actualRead);
                    cache.m_cacheLookaheadPos = 0;
                }

                if (returnValue == ResultCode::Error)
                {
                    AZ_TracePrintf(RemoteFileIOChannel, "RemoteFileIO::Read(fileHandle=%u, size=%u) -=CACHE READ=- actualRead=%i Failed", fileHandle, readSize, actualRead);
                }
                REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Read(fileHandle=%u, size=%u) -=CACHE READ=- actualRead=%i %s", fileHandle, readSize, actualRead, returnValue == ResultCode::Success ? "Success" : "Fail").c_str());
            }
            REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Read(fileHandle=%u, bytesThatHaveBeenRead=%u) return Success", fileHandle, bytesThatHaveBeenRead).c_str());
            return ResultCode::Success;
        }

        Result RemoteFileIO::Write(HandleType fileHandle, const void* buffer, AZ::u64 size, AZ::u64* bytesWritten)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::Write(fileHandle=%u, buffer=OUT, size=%u, bytesWritten=OUT)", fileHandle, size).c_str());
            // We need to seek back to where we should be in the file before we commit a write.
            // This is unnecessary if the cache is empty, or we're at the end of the cache.
            AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard);
            RemoteFileCache& cache = GetCache(fileHandle);
            if (cache.m_cacheLookaheadBuffer.size() && cache.RemainingBytes())
            {
                // find out where we are 
                AZ::u64 seekPosition = cache.CacheFilePosition();

                // note, seeks are predicted, and do not ask for a response.
                FileSeekRequest request(fileHandle, static_cast<AZ::u32>(AZ::IO::SeekType::SeekFromStart), seekPosition);
                if (!SendRequest(request))
                {
                    AZ_Assert(false, "RemoteFileIO::Write(fileHandle=%u, size=%u) Seek Failed to send request. return Error", fileHandle, size);
                    REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Write(fileHandle=%u, size=%u) Seek Failed to send request. return Error", fileHandle, size).c_str());
                    return ResultCode::Error;
                }

                cache.SetFilePosition(seekPosition);
            }

            cache.Invalidate();
            REMOTEFILE_LOG_APPEND("RemoteFileIO::Write() cache.Invalidate()");
            cache.m_fileSizeTime = 0; // invalidate file size after write.

            FileWriteRequest request(fileHandle, buffer, size);
            //always async and just return success unless bytesWritten is set then synchronous
            if (bytesWritten)
            {
                FileWriteResponse response;
                if (!SendRequest(request, response))
                {
                    AZ_Assert(false, "RemoteFileIO::Write(fileHandle=%u, size=%u) failed to send sync write request. return Error", fileHandle, size);
                    REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Write(fileHandle=%u, size=%u) failed to send sync write request. return Error", fileHandle, size).c_str());
                    return ResultCode::Error;
                }
                ResultCode res = static_cast<ResultCode>(response.m_resultCode);
                if (res == ResultCode::Error)
                {
                    AZ_TracePrintf(RemoteFileIOChannel, "RemoteFileIO::Write(fileHandle=%u, size=%u) sync write request failed", fileHandle, size);
                }
                *bytesWritten = response.m_bytesWritten;

                cache.OffsetFilePosition(response.m_bytesWritten);
            }
            else
            {
                // just send the message and assume we wrote it all successfully.
                if (!SendRequest(request))
                {
                    AZ_Assert(false, "RemoteFileIO::Write(fileHandle=%u, size=%u) failed to send async write request. return Error", fileHandle, size);
                    REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Write(fileHandle=%u, size=%u) failed to send async write request. return Error", fileHandle, size).c_str());
                    return ResultCode::Error;
                }
                cache.OffsetFilePosition(size);
            }
            REMOTEFILE_LOG_APPEND("RemoteFileIO::Write() Success");
            return ResultCode::Success;
        }

        bool RemoteFileIO::Eof(HandleType fileHandle)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::Eof(fileHandle=%u)", fileHandle).c_str());
            //make sure the cache file size is up to date
            AZ::u64 size = 0;
            Size(fileHandle, size);

            AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard);
            RemoteFileCache& cache = GetCache(fileHandle);
            bool isEof = cache.Eof();
            REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Eof(fileHandle=%u) return %s", fileHandle, isEof ? "True" : "False").c_str());
            return isEof;
        }

        //get cache should only be called while AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard);
        AZ::IO::RemoteFileCache& RemoteFileIO::GetCache(HandleType fileHandle)
        {
            auto found = m_remoteFileCache.find(fileHandle);
            // check this because it is a serious error since it may be that you're in an unguarded access to a non-existent handle.
            AZ_Assert(found != m_remoteFileCache.end(), "RemoteFileIO::GetCache(fileHandle=%u) Missing! Did something go wrong with open?", fileHandle);
            return found->second;
        }

        void RemoteFileIO::SetAlias(const char* alias, const char* path)
        {
            if (m_excludedFileIO)
            {
                m_excludedFileIO->SetAlias(alias, path);
            }
        }

        const char* RemoteFileIO::GetAlias(const char* alias)
        {
            return m_excludedFileIO ? m_excludedFileIO->GetAlias(alias) : nullptr;
        }

        void RemoteFileIO::ClearAlias(const char* alias)
        {
            if (m_excludedFileIO)
            {
                m_excludedFileIO->ClearAlias(alias);
            }
        }

        AZ::u64 RemoteFileIO::ConvertToAlias(char* inOutBuffer, AZ::u64 bufferLength) const
        {
            return m_excludedFileIO ? m_excludedFileIO->ConvertToAlias(inOutBuffer, bufferLength) : strlen(inOutBuffer);
        }

        bool RemoteFileIO::ResolvePath(const char* path, char* resolvedPath, AZ::u64 resolvedPathSize)
        {
            return m_excludedFileIO ? m_excludedFileIO->ResolvePath(path, resolvedPath, resolvedPathSize) : false;
        }

#ifdef REMOTEFILEIO_CACHE_FILETREE
        bool RemoteFileIO::Exists(const char* filePath)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::Exists(filePath=%s)", filePath?filePath:"nullptr").c_str());
            //error checks
            if (!filePath)
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Exists(filePath=nullptr) filePath=nullptr. return False").c_str());
                return false;
            }

            if (!strlen(filePath))
            {
                REMOTEFILE_LOG_APPEND("RemoteFileIO::Exists(filePath=\"\") strlen(filePath)==0. return False");
                return false;
            }

            AZStd::string filePathName(filePath);
            AZStd::replace(filePathName.begin(), filePathName.end(), '\\', '/');
            AZStd::size_t lastNonSlash = filePathName.find_last_not_of('/');
            AZStd::size_t lastSlash = filePathName.find_last_of('/');
            if (lastSlash != AZStd::string::npos && lastSlash > lastNonSlash)
            {
                filePathName.erase(lastSlash);
            }
            filePath = filePathName.c_str();

            const uint64_t numFiles = m_remoteFileTreeCache.size();
            for (uint64_t fileIdx = 0; fileIdx < numFiles; ++fileIdx)
            {
                const char* fileName = m_remoteFileTreeCache[fileIdx].c_str();
                if (!azstricmp(filePath, fileName))
                {
                    return true;
                }
            }

            const uint64_t numFolders = m_remoteFolderTreeCache.size();
            for (uint64_t folderIdx = 0; folderIdx < numFolders; ++folderIdx)
            {
                const char* folderName = m_remoteFolderTreeCache[folderIdx].c_str();
                if (!azstricmp(filePath, folderName))
                {
                    return true;
                }
            }

#ifdef REMOTEFILEIO_CACHE_FILETREE_FALLBACK
            //fall back
            bool bExists = NetworkFileIO::Exists(filePath);
            if (bExists)
            {
                //it wasn't found before, but it is now, update the cache
                if (NetworkFileIO::IsDirectory(filePath))
                {
                    m_remoteFolderTreeCache.push_back(filePath);
                }
                else
                {
                    m_remoteFileTreeCache.push_back(filePath);
                }
            }

            return bExists;
#else
            return false;
#endif
        }

        bool RemoteFileIO::IsDirectory(const char* filePath)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::IsDirectory(filePath=%s)", filePath?filepath:"nullptr").c_str());
            //error checks
            if (!filePath)
            {
                REMOTEFILE_LOG_APPEND("RemoteFileIO::IsDirectory(filePath=nullptr) filePath=nullptr. return False");
                return false;
            }

            if (!strlen(filePath))
            {
                REMOTEFILE_LOG_APPEND("RemoteFileIO::IsDirectory(filePath=\"\") strlen(filePath)==0. return False");
                return false;
            }

            AZStd::string filePathName(filePath);
            AZStd::replace(filePathName.begin(), filePathName.end(), '\\', '/');
            AZStd::size_t lastNonSlash = filePathName.find_last_not_of('/');
            AZStd::size_t lastSlash = filePathName.find_last_of('/');
            if (lastSlash != AZStd::string::npos && lastSlash > lastNonSlash)
            {
                filePathName.erase(lastSlash);
            }
            filePath = filePathName.c_str();

            const uint64_t numFolders = m_remoteFolderTreeCache.size();
            for (uint64_t folderIdx = 0; folderIdx < numFolders; ++folderIdx)
            {
                const char* folderName = m_remoteFolderTreeCache[folderIdx].c_str();
                if (!azstricmp(filePath, folderName))
                {
                    return true;
                }
            }

#ifdef REMOTEFILEIO_CACHE_FILETREE_FALLBACK
            //fallback
            bool bExists = NetworkFileIO::IsDirectory(filePath);
            if (bExists)
            {
                //it wasn't found before, but it is now, update the cache
                m_remoteFolderTreeCache.push_back(filePath);
            }
            return bExists;
#else
            return false;
#endif
        }

        Result RemoteFileIO::FindFiles(const char* filePath, const char* filter, FindFilesCallbackType callback)
        {
            REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::FindFiles(filePath=%s, filter=%s, callback=OUT)", filePath?filePath:"nullptr", filter?filter:"nullptr").c_str());
            //error checks
            if (!filePath)
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::FindFiles(filePath=nullptr, filter=%s) filePath=nullptr. return Error", filter?filter:"nullptr").c_str());
                return ResultCode::Error;
            }

            if (!filter)
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::FindFiles(filePath=%s, filter=nullptr) filter=nullptr. return Error", filePath).c_str());
                return ResultCode::Error;
            }

            if (!strlen(filePath))
            {
                REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::FindFiles(filePath=\"\", filter=%s) strlen(filePath)==0. return Error", filter).c_str());
                return ResultCode::Error;
            }

            AZStd::string filePathName(filePath);
            AZStd::replace(filePathName.begin(), filePathName.end(), '\\', '/');
            AZStd::size_t lastNonSlash = filePathName.find_last_not_of('/');
            AZStd::size_t lastSlash = filePathName.find_last_of('/');
            if (lastSlash != AZStd::string::npos && lastSlash > lastNonSlash)
            {
                filePathName.erase(lastSlash);
            }
            filePath = filePathName.c_str();
            AZ::u64 filePathLen = filePathName.length();

            // files callbacks
            const uint64_t numFiles = m_remoteFileTreeCache.size();
            for (uint64_t fileIdx = 0; fileIdx < numFiles; ++fileIdx)
            {
                const char* cachedFilePath = m_remoteFileTreeCache[fileIdx].c_str();
                if (!azstrnicmp(filePath, cachedFilePath, filePathLen))
                {
                    if (strlen(cachedFilePath) > filePathLen)
                    {
                        const char* cachedFileName = cachedFilePath + filePathLen + 1;
                        if (!strchr(cachedFileName, '/'))
                        {
                            //no slash was found so this file is in this folder
                            if (AZStd::wildcard_match(filter, cachedFileName))
                            {
                                if (!callback(cachedFilePath))
                                {
                                    return ResultCode::Success;
                                }
                            }
                        }
                    }
                }
            }

            // folders
            const uint64_t numFolders = m_remoteFolderTreeCache.size();
            for (uint64_t folderIdx = 0; folderIdx < numFolders; ++folderIdx)
            {
                const char* cachedFolderPath = m_remoteFolderTreeCache[folderIdx].c_str();
                if (!azstrnicmp(filePath, cachedFolderPath, filePathLen))
                {
                    if (strlen(cachedFolderPath) > filePathLen)
                    {
                        const char* cachedFolderName = cachedFolderPath + filePathLen + 1;
                        if (!strchr(cachedFolderName, '/'))
                        {
                            //no slash was found so this is not a sub folder
                            if (AZStd::wildcard_match(filter, cachedFolderName))
                            {
                                if (!callback(cachedFolderPath))
                                {
                                    return ResultCode::Success;
                                }
                            }
                        }
                    }
                }
            }

            return ResultCode::Success;
        }

        Result RemoteFileIO::CacheFileTree()
        {
            m_remoteFileTreeCache.clear();
            m_remoteFolderTreeCache.clear();

            REMOTEFILE_LOG_CALL("RemoteFileIO()::CacheFileTree()");
            FileTreeRequest request;
            FileTreeResponse response;
            if (!SendRequest(request, response))
            {
                AZ_Assert(false, "RemoteFileIO::CacheFileTree() could not send request. return Error");
                REMOTEFILE_LOG_APPEND("RemoteFileIO::CacheFileTree() could not send request. return Error");
                return ResultCode::Error;
            }

            Result returnValue = static_cast<ResultCode>(response.m_resultCode);
            if (returnValue == ResultCode::Success)
            {
                for (auto& it: response.m_fileList)
                {
                    AZStd::replace(it.begin(), it.end(), '\\', '/');
                    m_remoteFileTreeCache.push_back(it);
                }

                for (auto& it: response.m_folderList)
                {
                    AZStd::replace(it.begin(), it.end(), '\\', '/');
                    AZStd::size_t lastNonSlash = it.find_last_not_of('/');
                    AZStd::size_t lastSlash = it.find_last_of('/');
                    if (lastSlash != AZStd::string::npos && lastSlash > lastNonSlash)
                    {
                        it.erase(lastSlash);
                    }
                    m_remoteFolderTreeCache.push_back(it);
                }
            }

            AzFramework::AssetCatalogEventBus::Handler::BusConnect();

            return returnValue;
        }

        //=========================================================================
        // AssetCatalogEventBus::OnCatalogAssetChanged
        //=========================================================================
        void RemoteFileIO::OnCatalogAssetChanged(const AZ::Data::AssetId& assetId)
        {
            AZ_UNUSED(assetId);
            CacheFileTree();
        }

        //=========================================================================
        // AssetSystemBus::OnCatalogAssetRemoved
        //=========================================================================
        void RemoteFileIO::OnCatalogAssetRemoved(const AZ::Data::AssetId& assetId)
        {
            AZ_UNUSED(assetId);
            CacheFileTree();
        }
#endif //REMOTEFILEIO_CACHE_FILETREE
#endif //REMOTEFILEIO_IS_NETWORKFILEIO
    } // namespace IO
}//namespace AZ