//===-- PlatformPOSIX.cpp ---------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "PlatformPOSIX.h"

// C Includes
// C++ Includes
// Other libraries and framework includes
// Project includes

#include "lldb/Core/DataBufferHeap.h"
#include "lldb/Core/Log.h"
#include "lldb/Core/StreamString.h"
#include "lldb/Host/File.h"
#include "lldb/Host/FileSpec.h"
#include "lldb/Host/Host.h"

using namespace lldb;
using namespace lldb_private;


//------------------------------------------------------------------
/// Default Constructor
//------------------------------------------------------------------
PlatformPOSIX::PlatformPOSIX (bool is_host) :
Platform(is_host),  // This is the local host platform
m_remote_platform_sp ()
{
}

//------------------------------------------------------------------
/// Destructor.
///
/// The destructor is virtual since this class is designed to be
/// inherited from by the plug-in instance.
//------------------------------------------------------------------
PlatformPOSIX::~PlatformPOSIX()
{
}

lldb_private::OptionGroupOptions*
PlatformPOSIX::GetConnectionOptions (lldb_private::CommandInterpreter& interpreter)
{
    if (m_options.get() == NULL)
    {
        m_options.reset(new OptionGroupOptions(interpreter));
        m_options->Append(new OptionGroupPlatformRSync());
        m_options->Append(new OptionGroupPlatformSSH());
        m_options->Append(new OptionGroupPlatformCaching());
    }
    return m_options.get();
}

lldb_private::Error
PlatformPOSIX::RunShellCommand (const char *command,           // Shouldn't be NULL
                                const char *working_dir,       // Pass NULL to use the current working directory
                                int *status_ptr,               // Pass NULL if you don't want the process exit status
                                int *signo_ptr,                // Pass NULL if you don't want the signal that caused the process to exit
                                std::string *command_output,   // Pass NULL if you don't want the command output
                                uint32_t timeout_sec)         // Timeout in seconds to wait for shell program to finish
{
    if (IsHost())
        return Host::RunShellCommand(command, working_dir, status_ptr, signo_ptr, command_output, timeout_sec);
    else
    {
        if (m_remote_platform_sp)
            return m_remote_platform_sp->RunShellCommand(command, working_dir, status_ptr, signo_ptr, command_output, timeout_sec);
        else
            return Error("unable to run a remote command without a platform");
    }
}

uint32_t
PlatformPOSIX::MakeDirectory (const std::string &path,
                               mode_t mode)
{
    if (IsHost())
    {
        return Host::MakeDirectory (path.c_str(), mode);
    }
    if (IsRemote() && m_remote_platform_sp)
        return m_remote_platform_sp->MakeDirectory(path, mode);
    return Platform::MakeDirectory(path,mode);
}

lldb::user_id_t
PlatformPOSIX::OpenFile (const FileSpec& file_spec,
                         uint32_t flags,
                         mode_t mode,
                         Error &error)
{
    if (IsHost())
    {
        return Host::OpenFile(file_spec, flags, mode, error);
    }
    if (IsRemote() && m_remote_platform_sp)
        return m_remote_platform_sp->OpenFile(file_spec, flags, mode, error);
    return Platform::OpenFile(file_spec, flags, mode, error);
}

bool
PlatformPOSIX::CloseFile (lldb::user_id_t fd, Error &error)
{
    if (IsHost())
    {
        return Host::CloseFile(fd, error);
    }
    if (IsRemote() && m_remote_platform_sp)
        return m_remote_platform_sp->CloseFile(fd, error);
    return Platform::CloseFile(fd, error);
}

uint64_t
PlatformPOSIX::ReadFile (lldb::user_id_t fd,
                         uint64_t offset,
                         void *dst,
                         uint64_t dst_len,
                         Error &error)
{
    if (IsHost())
    {
        return Host::ReadFile(fd, offset, dst, dst_len, error);
    }
    if (IsRemote() && m_remote_platform_sp)
        return m_remote_platform_sp->ReadFile(fd, offset, dst, dst_len, error);
    return Platform::ReadFile(fd, offset, dst, dst_len, error);
}

uint64_t
PlatformPOSIX::WriteFile (lldb::user_id_t fd,
                          uint64_t offset,
                          const void* src,
                          uint64_t src_len,
                          Error &error)
{
    if (IsHost())
    {
        return Host::WriteFile(fd, offset, src, src_len, error);
    }
    if (IsRemote() && m_remote_platform_sp)
        return m_remote_platform_sp->WriteFile(fd, offset, src, src_len, error);

    return Platform::WriteFile(fd, offset, src, src_len, error);
}

static uint32_t
chown_file(Platform *platform,
           const char* path,
           uint32_t uid = UINT32_MAX,
           uint32_t gid = UINT32_MAX)
{
    if (!platform || !path || *path == 0)
        return UINT32_MAX;
    
    if (uid == UINT32_MAX && gid == UINT32_MAX)
        return 0;   // pretend I did chown correctly - actually I just didn't care
    
    StreamString command;
    command.PutCString("chown ");
    if (uid != UINT32_MAX)
        command.Printf("%d",uid);
    if (gid != UINT32_MAX)
        command.Printf(":%d",gid);
    command.Printf("%s",path);
    int status;
    platform->RunShellCommand(command.GetData(),
                              NULL,
                              &status,
                              NULL,
                              NULL,
                              10);
    return status;
}

lldb_private::Error
PlatformPOSIX::PutFile (const lldb_private::FileSpec& source,
                         const lldb_private::FileSpec& destination,
                         uint32_t uid,
                         uint32_t gid)
{
    if (IsHost())
    {
        if (FileSpec::Equal(source, destination, true))
            return Error();
        // cp src dst
        // chown uid:gid dst
        std::string src_path (source.GetPath());
        if (src_path.empty())
            return Error("unable to get file path for source");
        std::string dst_path (destination.GetPath());
        if (dst_path.empty())
            return Error("unable to get file path for destination");
        StreamString command;
        command.Printf("cp %s %s", src_path.c_str(), dst_path.c_str());
        int status;
        RunShellCommand(command.GetData(),
                        NULL,
                        &status,
                        NULL,
                        NULL,
                        10);
        if (status != 0)
            return Error("unable to perform copy");
        if (uid == UINT32_MAX && gid == UINT32_MAX)
            return Error();
        if (chown_file(this,dst_path.c_str(),uid,gid) != 0)
            return Error("unable to perform chown");
        return Error();
    }
    else if (IsRemote() && m_remote_platform_sp)
    {
        if (GetSupportsRSync())
        {
            std::string src_path (source.GetPath());
            if (src_path.empty())
                return Error("unable to get file path for source");
            std::string dst_path (destination.GetPath());
            if (dst_path.empty())
                return Error("unable to get file path for destination");
            StreamString command;
            if (GetIgnoresRemoteHostname())
            {
                if (!GetRSyncPrefix())
                    command.Printf("rsync %s %s %s",
                                   GetRSyncOpts(),
                                   src_path.c_str(),
                                   dst_path.c_str());
                else
                    command.Printf("rsync %s %s %s%s",
                                   GetRSyncOpts(),
                                   src_path.c_str(),
                                   GetRSyncPrefix(),
                                   dst_path.c_str());
            }
            else
                command.Printf("rsync %s %s %s:%s",
                               GetRSyncOpts(),
                               src_path.c_str(),
                               GetHostname(),
                               dst_path.c_str());
            Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PLATFORM));
            if (log)
                log->Printf("[PutFile] Running command: %s\n", command.GetData());
            int retcode;
            Host::RunShellCommand(command.GetData(),
                                  NULL,
                                  &retcode,
                                  NULL,
                                  NULL,
                                  60);
            if (retcode == 0)
            {
                // Don't chown a local file for a remote system
//                if (chown_file(this,dst_path.c_str(),uid,gid) != 0)
//                    return Error("unable to perform chown");
                return Error();
            }
            // if we are still here rsync has failed - let's try the slow way before giving up
        }
        // open
        // read, write, read, write, ...
        // close
        // chown uid:gid dst
        Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PLATFORM));
            if (log)
                log->Printf("[PutFile] Using block by block transfer....\n");
        File source_file(source, File::eOpenOptionRead, File::ePermissionsUserRW);
        if (!source_file.IsValid())
            return Error("unable to open source file");
        Error error;
        lldb::user_id_t dest_file = OpenFile (destination,
                                              File::eOpenOptionCanCreate | File::eOpenOptionWrite | File::eOpenOptionTruncate,
                                              File::ePermissionsUserRWX | File::ePermissionsGroupRX | File::ePermissionsWorldRX,
                                              error);
        if (log)
            log->Printf ("dest_file = %" PRIu64 "\n", dest_file);
        if (error.Fail())
            return error;
        if (dest_file == UINT64_MAX)
            return Error("unable to open target file");
        lldb::DataBufferSP buffer_sp(new DataBufferHeap(1024, 0));
        uint64_t offset = 0;
        while (error.Success())
        {
            size_t bytes_read = buffer_sp->GetByteSize();
            error = source_file.Read(buffer_sp->GetBytes(), bytes_read);
            if (bytes_read)
            {
                WriteFile(dest_file, offset, buffer_sp->GetBytes(), bytes_read, error);
                offset += bytes_read;
            }
            else
                break;
        }
        CloseFile(dest_file, error);
        if (uid == UINT32_MAX && gid == UINT32_MAX)
            return error;
        // This is remopve, don't chown a local file...
//        std::string dst_path (destination.GetPath());
//        if (chown_file(this,dst_path.c_str(),uid,gid) != 0)
//            return Error("unable to perform chown");
        return error;
    }
    return Platform::PutFile(source,destination,uid,gid);
}

lldb::user_id_t
PlatformPOSIX::GetFileSize (const FileSpec& file_spec)
{
    if (IsHost())
    {
        return Host::GetFileSize(file_spec);
    }
    if (IsRemote() && m_remote_platform_sp)
        return m_remote_platform_sp->GetFileSize(file_spec);
    return Platform::GetFileSize(file_spec);
}

bool
PlatformPOSIX::GetFileExists (const FileSpec& file_spec)
{
    if (IsHost())
    {
        return file_spec.Exists();
    }
    if (IsRemote() && m_remote_platform_sp)
        return m_remote_platform_sp->GetFileExists(file_spec);
    return Platform::GetFileExists(file_spec);
}

uint32_t
PlatformPOSIX::GetFilePermissions (const lldb_private::FileSpec &file_spec,
                                   lldb_private::Error &error)
{
    if (IsHost())
    {
        return File::GetPermissions(file_spec.GetPath().c_str(), error);
    }
    if (IsRemote() && m_remote_platform_sp)
        return m_remote_platform_sp->GetFilePermissions(file_spec, error);
    return Platform::GetFilePermissions(file_spec, error);
    
}


lldb_private::Error
PlatformPOSIX::GetFile (const lldb_private::FileSpec& source /* remote file path */,
                         const lldb_private::FileSpec& destination /* local file path */)
{
    // Check the args, first.
    std::string src_path (source.GetPath());
    if (src_path.empty())
        return Error("unable to get file path for source");
    std::string dst_path (destination.GetPath());
    if (dst_path.empty())
        return Error("unable to get file path for destination");
    if (IsHost())
    {
        if (FileSpec::Equal(source, destination, true))
            return Error("local scenario->source and destination are the same file path: no operation performed");
        // cp src dst
        StreamString cp_command;
        cp_command.Printf("cp %s %s", src_path.c_str(), dst_path.c_str());
        int status;
        RunShellCommand(cp_command.GetData(),
                        NULL,
                        &status,
                        NULL,
                        NULL,
                        10);
        if (status != 0)
            return Error("unable to perform copy");
        return Error();
    }
    else if (IsRemote() && m_remote_platform_sp)
    {
        if (GetSupportsRSync())
        {
            StreamString command;
            if (GetIgnoresRemoteHostname())
            {
                if (!GetRSyncPrefix())
                    command.Printf("rsync %s %s %s",
                                   GetRSyncOpts(),
                                   src_path.c_str(),
                                   dst_path.c_str());
                else
                    command.Printf("rsync %s %s%s %s",
                                   GetRSyncOpts(),
                                   GetRSyncPrefix(),
                                   src_path.c_str(),
                                   dst_path.c_str());
            }
            else
                command.Printf("rsync %s %s:%s %s",
                               GetRSyncOpts(),
                               m_remote_platform_sp->GetHostname(),
                               src_path.c_str(),
                               dst_path.c_str());
            Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PLATFORM));
            if (log)
                log->Printf("[GetFile] Running command: %s\n", command.GetData());
            int retcode;
            Host::RunShellCommand(command.GetData(),
                                  NULL,
                                  &retcode,
                                  NULL,
                                  NULL,
                                  60);
            if (retcode == 0)
                return Error();
            // If we are here, rsync has failed - let's try the slow way before giving up
        }
        // open src and dst
        // read/write, read/write, read/write, ...
        // close src
        // close dst
        Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PLATFORM));
            if (log)
                log->Printf("[GetFile] Using block by block transfer....\n");
        Error error;
        user_id_t fd_src = OpenFile (source,
                                     File::eOpenOptionRead,
                                     File::ePermissionsDefault,
                                     error);

        if (fd_src == UINT64_MAX)
            return Error("unable to open source file");

        uint32_t permissions = GetFilePermissions(source, error);
        
        if (permissions == 0)
            permissions = File::ePermissionsDefault;

        user_id_t fd_dst = Host::OpenFile(destination,
                                          File::eOpenOptionCanCreate | File::eOpenOptionWrite | File::eOpenOptionTruncate,
                                          permissions,
                                          error);

        if (fd_dst == UINT64_MAX)
        {
            if (error.Success())
                error.SetErrorString("unable to open destination file");
        }

        if (error.Success())
        {
            lldb::DataBufferSP buffer_sp(new DataBufferHeap(1024, 0));
            uint64_t offset = 0;
            error.Clear();
            while (error.Success())
            {
                const uint64_t n_read = ReadFile (fd_src,
                                                  offset,
                                                  buffer_sp->GetBytes(),
                                                  buffer_sp->GetByteSize(),
                                                  error);
                if (error.Fail())
                    break;
                if (n_read == 0)
                    break;
                if (Host::WriteFile(fd_dst,
                                    offset,
                                    buffer_sp->GetBytes(),
                                    n_read,
                                    error) != n_read)
                {
                    if (!error.Fail())
                        error.SetErrorString("unable to write to destination file");
                        break;
                }
                offset += n_read;
            }
        }
        // Ignore the close error of src.
        if (fd_src != UINT64_MAX)
            CloseFile(fd_src, error);
        // And close the dst file descriptot.
        if (fd_dst != UINT64_MAX && !Host::CloseFile(fd_dst, error))
        {
            if (!error.Fail())
                error.SetErrorString("unable to close destination file");

        }
        return error;
    }
    return Platform::GetFile(source,destination);
}

std::string
PlatformPOSIX::GetPlatformSpecificConnectionInformation()
{
    StreamString stream;
    if (GetSupportsRSync())
    {
        stream.PutCString("rsync");
        if ( (GetRSyncOpts() && *GetRSyncOpts()) ||
             (GetRSyncPrefix() && *GetRSyncPrefix()) ||
             GetIgnoresRemoteHostname())
        {
            stream.Printf(", options: ");
            if (GetRSyncOpts() && *GetRSyncOpts())
                stream.Printf("'%s' ",GetRSyncOpts());
            stream.Printf(", prefix: ");
            if (GetRSyncPrefix() && *GetRSyncPrefix())
                stream.Printf("'%s' ",GetRSyncPrefix());
            if (GetIgnoresRemoteHostname())
                stream.Printf("ignore remote-hostname ");
        }
    }
    if (GetSupportsSSH())
    {
        stream.PutCString("ssh");
        if (GetSSHOpts() && *GetSSHOpts())
            stream.Printf(", options: '%s' ",GetSSHOpts());
    }
    if (GetLocalCacheDirectory() && *GetLocalCacheDirectory())
        stream.Printf("cache dir: %s",GetLocalCacheDirectory());
    if (stream.GetSize())
        return stream.GetData();
    else
        return "";
}

bool
PlatformPOSIX::CalculateMD5 (const FileSpec& file_spec,
                            uint64_t &low,
                            uint64_t &high)
{
    if (IsHost())
        return Platform::CalculateMD5 (file_spec, low, high);
    if (m_remote_platform_sp)
        return m_remote_platform_sp->CalculateMD5(file_spec, low, high);
    return false;
}
