/*
  ==============================================================================

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-6 by Raw Material Software ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified under the terms of the
   GNU General Public License, as published by the Free Software Foundation;
   either version 2 of the License, or (at your option) any later version.

   JUCE is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with JUCE; if not, visit www.gnu.org/licenses or write to the
   Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
   Boston, MA 02111-1307 USA

  ------------------------------------------------------------------------------

   If you'd like to release a closed-source product which uses JUCE, commercial
   licenses are also available: visit www.rawmaterialsoftware.com/juce for
   more information.

  ==============================================================================
*/

#include "linuxincludes.h"
#include "../../../src/juce_core/basics/juce_StandardHeader.h"

#include <sys/stat.h>
#include <sys/dir.h>
#include <sys/ptrace.h>
#include <sys/vfs.h>        // for statfs
#include <sys/wait.h>
#include <unistd.h>
#include <fnmatch.h>
#include <utime.h>
#include <pwd.h>
#include <fcntl.h>

#define U_ISOFS_SUPER_MAGIC     (short) 0x9660   // linux/iso_fs.h
#define U_MSDOS_SUPER_MAGIC     (short) 0x4d44   // linux/msdos_fs.h
#define U_NFS_SUPER_MAGIC       (short) 0x6969   // linux/nfs_fs.h
#define U_SMB_SUPER_MAGIC       (short) 0x517B   // linux/smb_fs.h


BEGIN_JUCE_NAMESPACE

#include "../../../src/juce_core/io/files/juce_FileInputStream.h"
#include "../../../src/juce_core/io/files/juce_FileOutputStream.h"
#include "../../../src/juce_core/basics/juce_SystemStats.h"
#include "../../../src/juce_core/basics/juce_Time.h"
#include "../../../src/juce_core/basics/juce_Logger.h"
#include "../../../src/juce_core/io/network/juce_URL.h"
#include "../../../src/juce_core/io/files/juce_NamedPipe.h"


//==============================================================================
bool juce_isDirectory (const String& fileName)
{
    if (fileName.isEmpty())
        return true;

    struct stat info;
    const int res = stat ((const char*) fileName, &info);
    if (res == 0)
        return (info.st_mode & S_IFDIR) != 0;

    return false;
}

bool juce_fileExists (const String& fileName, const bool dontCountDirectories)
{
    if (fileName.isEmpty())
        return ! dontCountDirectories;

    bool exists = access ((const char*) fileName, F_OK) == 0;

    if (exists && dontCountDirectories && juce_isDirectory (fileName))
        exists = false;

    return exists;
}

int64 juce_getFileSize (const String& fileName)
{
    struct stat info;
    const int res = stat ((const char*) fileName, &info);

    if (res == 0)
        return info.st_size;

    return 0;
}

void juce_getFileTimes (const String& fileName,
                        int64& modificationTime,
                        int64& accessTime,
                        int64& creationTime)
{
    modificationTime = 0;
    accessTime = 0;
    creationTime = 0;

    struct stat info;
    const int res = stat ((const char*)fileName, &info);
    if (res == 0)
    {
        /*
         * Note: On Linux the st_ctime field is defined as last change time
         * rather than creation.
         */
        modificationTime = (int64) info.st_mtime * 1000;
        accessTime = (int64) info.st_atime * 1000;
        creationTime = (int64) info.st_ctime * 1000;
    }
}

bool juce_setFileTimes (const String& fileName,
                        int64 modificationTime,
                        int64 accessTime,
                        int64 creationTime)
{
    struct utimbuf times;
    times.actime = (time_t) (accessTime / 1000);
    times.modtime = (time_t) (modificationTime / 1000);

    return utime ((const char*) fileName, &times) == 0;
}

bool juce_canWriteToFile (const String& fileName)
{
    return access ((const char*) fileName, W_OK) == 0;
}

bool juce_setFileReadOnly (const String& fileName, bool isReadOnly)
{
    struct stat info;
    const int res = stat ((const char*) fileName, &info);
    if (res != 0)
        return false;

    info.st_mode &= 0777;   // Just permissions

    if( isReadOnly )
        info.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
    else
        // Give everybody write permission?
        info.st_mode |= S_IWUSR | S_IWGRP | S_IWOTH;

    return chmod ((const char*) fileName, info.st_mode) == 0;
}

bool juce_deleteFile (const String& fileName)
{
    if (juce_isDirectory (fileName))
        return rmdir ((const char*) fileName) == 0;
    else
        return remove ((const char*) fileName) == 0;
}

bool juce_copyFile (const String& s, const String& d)
{
    const File source (s), dest (d);

    FileInputStream* in = source.createInputStream();
    bool ok = false;

    if (in != 0)
    {
        if (dest.deleteFile())
        {
            FileOutputStream* const out = dest.createOutputStream();

            if (out != 0)
            {
                const int bytesCopied = out->writeFromInputStream (*in, -1);
                delete out;

                ok = (bytesCopied == source.getSize());

                if (! ok)
                    dest.deleteFile();
            }
        }

        delete in;
    }

    return ok;
}

bool juce_moveFile (const String& source, const String& dest)
{
    if (rename ((const char*) source, (const char*) dest) == 0)
        return true;

    if (! juce_canWriteToFile (source))
        return false;

    if (juce_copyFile (source, dest))
    {
        if (juce_deleteFile (source))
            return true;

        juce_deleteFile (dest);
    }

    return false;
}

void juce_createDirectory (const String& fileName)
{
    mkdir ((const char*) fileName, 0777);
}

void* juce_fileOpen (const String& fileName, bool forWriting)
{
    const char* mode = "rb";

    if (forWriting)
    {
        if (juce_fileExists (fileName, false))
        {
            FILE* f = fopen ((const char*) fileName, "r+b");
            if (f != 0)
                fseek (f, 0, SEEK_END);

            return (void*)f;
        }
        else
        {
            mode = "w+b";
        }
    }

    return (void*)fopen ((const char*) fileName, mode);
}

void juce_fileClose (void* handle)
{
    if (handle != 0)
        fclose ((FILE*) handle);
}

int juce_fileRead (void* handle, void* buffer, int size)
{
    if (handle != 0)
        return fread (buffer, 1, size, (FILE*) handle);

    return 0;
}

int juce_fileWrite (void* handle, const void* buffer, int size)
{
    if (handle != 0)
        return fwrite (buffer, 1, size, (FILE*) handle);

    return 0;
}

int64 juce_fileSetPosition (void* handle, int64 pos)
{
    if (handle != 0 && fseek ((FILE*) handle, (long) pos, SEEK_SET) == 0)
        return pos;

    return -1;
}

int64 juce_fileGetPosition (void* handle)
{
    if (handle != 0)
        return ftell ((FILE*) handle);
    else
        return -1;
}

void juce_fileFlush (void* handle)
{
    if (handle != 0)
        fflush ((FILE*) handle);
}

const StringArray juce_getFileSystemRoots()
{
    StringArray s;
    s.add (T("/"));
    return s;
}

const String juce_getVolumeLabel (const String& filenameOnVolume,
                                  int& volumeSerialNumber)
{
    // There is no equivalent on Linux
    volumeSerialNumber = 0;
    return String::empty;
}

int64 File::getBytesFreeOnVolume() const throw()
{
    struct statfs buf;
    int64 free_space = 0;

    if (statfs ((const char*) getFullPathName(), &buf) == 0)
    {
        // Note: this returns space available to non-super user
        free_space = (int64) buf.f_bsize * (int64) buf.f_bavail;
    }

    return free_space;
}

bool File::isOnCDRomDrive() const throw()
{
    struct statfs buf;

    if (statfs((const char*)getFullPathName(), &buf) == 0)
        return (buf.f_type == U_ISOFS_SUPER_MAGIC);

    // Assume not if this fails for some reason
    return false;
}

bool File::isOnHardDisk() const throw()
{
    struct statfs buf;

    if (statfs((const char*)getFullPathName(), &buf) == 0)
    {
        switch (buf.f_type)
        {
            case U_ISOFS_SUPER_MAGIC:   // CD-ROM
            case U_MSDOS_SUPER_MAGIC:   // Probably floppy (but could be mounted FAT filesystem)
            case U_NFS_SUPER_MAGIC:     // Network NFS
            case U_SMB_SUPER_MAGIC:     // Network Samba
                return false;

            default:
                // Assume anything else is a hard-disk (but note it could
                // be a RAM disk.  There isn't a good way of determining
                // this for sure)
                return true;
        }
    }

    // Assume so if this fails for some reason
    return true;
}

const String SystemStats::getTempPath()
{
    String tmp (T("/tmp"));

    if (! juce_isDirectory (tmp))
        tmp = File::getCurrentWorkingDirectory().getFullPathName();

    return tmp;
}

const String SystemStats::getUserHomeDirectory()
{
    const char* homeDir = getenv ("HOME");

    if (homeDir == 0)
    {
        struct passwd* const pw = getpwuid (getuid());
        if (pw != 0)
            homeDir = pw->pw_dir;
    }

    String home (homeDir);
    if (home.endsWithChar (T('/')))
        home = home.dropLastCharacters(1);

    return home;
}

const String SystemStats::getUserDocumentsDirectory()
{
    return getUserHomeDirectory();
}

const String SystemStats::getUserApplicationDataDirectory()
{
    return getUserHomeDirectory();
}

//==============================================================================
static String executableFilename;

const String SystemStats::getCurrentExecutableFileName()
{
    return executableFilename;
}

void juce_setCurrentExecutableFileName (const String& filename)
{
    executableFilename = filename;
}

//==============================================================================
const File File::getCurrentWorkingDirectory() throw()
{
    char buf [2048];
    getcwd (buf, sizeof(buf));
    return File (buf);
}

bool File::setAsCurrentWorkingDirectory() const throw()
{
    return chdir ((const char*)getFullPathName()) == 0;
}

//==============================================================================
const tchar File::separator = T('/');
const tchar* File::separatorString = T("/");

//==============================================================================
struct FindFileStruct
{
    String parentDir, wildCard;
    DIR* dir;

    bool getNextMatch (String& result, bool& isDir)
    {
        for (;;)
        {
            struct dirent* de = readdir (dir);

            if (de == 0)
                break;

            if (fnmatch ((const char*) wildCard, de->d_name, FNM_CASEFOLD) == 0)
            {
                result = de->d_name;
                isDir = juce_isDirectory (parentDir + result);
                return true;
            }
        }

        return false;
    }
};

// returns 0 on failure
void* juce_findFileStart (const String& directory,
                          const String& wildCard,
                          String& firstResult,
                          bool& isDir)
{
    DIR* d = opendir ((const char*) directory);

    if (d != 0)
    {
        FindFileStruct* ff = new FindFileStruct();
        ff->parentDir = directory;

        if (!ff->parentDir.endsWithChar (File::separator))
            ff->parentDir += File::separator;

        ff->wildCard = wildCard;
        if (wildCard == T("*.*"))
            ff->wildCard = T("*");

        ff->dir = d;

        if (ff->getNextMatch (firstResult, isDir))
        {
            return ff;
        }
        else
        {
            firstResult = String::empty;
            isDir = false;
            closedir (d);
            delete ff;
        }
    }

    return 0;
}

bool juce_findFileNext (void* handle, String& resultFile, bool& isDir)
{
    FindFileStruct* ff = (FindFileStruct*) handle;

    if (ff != 0)
        return ff->getNextMatch (resultFile, isDir);

    return false;
}

void juce_findFileClose (void* handle)
{
    FindFileStruct* ff = (FindFileStruct*) handle;

    if (ff != 0)
    {
        closedir (ff->dir);
        delete ff;
    }
}

// Necessary to avoid terminated child processes becoming zombified
static void sigchild_handler(int sig)
{
    // This is also entered when the child stops
    waitpid(-1, NULL, WNOHANG);
}

bool juce_launchFile (const String& fileName,
                      const String& parameters)
{
#ifndef __CYGWIN__
    // This won't work if it's a URL
    static bool childHandlerSetup = false;

    String cmdString (fileName);
    cmdString << " " << parameters;

    char* const argv[4] = { "/bin/sh", "-c", (char*) (const char*) cmdString, 0 };

    if (childHandlerSetup == false)
    {
        struct sigaction saction;
        sigset_t maskSet;
        sigemptyset (&maskSet);
        saction.sa_handler = sigchild_handler;
        saction.sa_mask = maskSet;
        saction.sa_flags = 0;
        sigaction (SIGCLD, &saction, 0);
        childHandlerSetup = true;
    }

    const int cpid = fork();

    if (cpid == 0)
    {
        // Child process
        ptrace (PTRACE_TRACEME, 0, 0, 0);

        if (execve (argv[0], argv, environ) < 0)
            exit (0);
    }
    else
    {
        if (cpid < 0)
            return false;

        // Wait for child process
        int status;
        waitpid (cpid, &status, WUNTRACED);

        if (WIFSTOPPED (status))
        {
            // Child process stopped at SIGTRAP caused by
            // successful execve, now let it continue
            ptrace (PTRACE_DETACH, cpid, 0, 0);
        }
        else
        {
            // Child process exited, meaning execve failed
            return false;
        }
    }

    return true;
#else
    jassertfalse // cygwin doesn't seem to have the headers for doing this..
    return false;
#endif
}


END_JUCE_NAMESPACE
