//#####################################################################
// Copyright 2002-2007, Ronald Fedkiw, Eran Guendelman, Geoffrey Irving, Igor Neverov, Andrew Selle.
// This file is part of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYRIGHT.txt.
//#####################################################################
#include <Core/Read_Write/FILE_UTILITIES.h>
#include <Core/Arrays/ARRAY.h>
#include <Core/Log/DEBUG_UTILITIES.h>
#include <Core/Log/LOG.h>
#include <Core/Read_Write/ZIP.h>
#include <cerrno>
#include <climits>
#include <cstdio>
#include <fstream>
#ifdef _WIN32
#define NOMINMAX
#include <windows.h>
#endif
#if defined(__linux__) || defined(__CYGWIN__) || defined(__APPLE__)
#include <sys/stat.h>
#endif
namespace PhysBAM{

//###################################################################
// Win32 Specific Function Definitions
//###################################################################
#if defined(_WIN32)

FILE* Temporary_File()
{
    return fopen(_tempnam(getenv("TEMP"),"pb"),"w");
}

//###################################################################
// Linux Specific Function Definitions
//###################################################################
#elif defined(__linux__) || defined(__CYGWIN__) || defined(__APPLE__)
FILE* Temporary_File()
{
    return tmpfile();
}

//###################################################################
// Default (Unimplemented) Function Definitions
//###################################################################
#else

FILE* Temporary_File()
{
    PHYSBAM_NOT_IMPLEMENTED();
}

#endif
//###################################################################
// File open with compression capability
//###################################################################
std::istream* Safe_Open_Input_Raw(const std::string& filename,bool binary)
{
    bool compressed=File_Is_Compressed(filename);
    std::ios_base::openmode flags=std::ios::in;if(binary) flags|=std::ios::binary;
    std::string filename_compressed=compressed?filename:filename+".gz";
#ifndef COMPILE_WITHOUT_ZLIB_SUPPORT
    if(File_Exists(filename_compressed))
        return Gzip_In(filename_compressed,flags);
#else
    if(File_Exists(filename_compressed))
        PHYSBAM_FATAL_ERROR("Must compile with zlib to read compressed files");
#endif
    if(!compressed){
        std::istream* input=new std::ifstream(filename.c_str(),flags);if(*input) return input;
        filename_compressed=filename+"(.gz)";}
    LOG::cerr<<"Can't open "<<filename_compressed<<" for read "<<(binary?"(binary)":"")<<std::endl;
    throw FILESYSTEM_ERROR("Safe_Open_Input failed");
}
std::ostream* Safe_Open_Output_Raw(const std::string& filename,bool binary,bool write_compressed_if_possible)
{
    bool compressed=File_Is_Compressed(filename);
    std::ios_base::openmode flags=std::ios::out;if(binary) flags|=std::ios::binary;
    if(!write_compressed_if_possible && fs::exists(filename+".gz")){
        LOG::cerr<<"Refusing to write "<<filename<<" uncompressed when compressed version already exists\n";
        throw FILESYSTEM_ERROR("Safe_Open_Output failed (compressed already exists)");}
    if(compressed && !binary){LOG::cerr<<"Refusing to open compressed file "<<filename<<"in text mode\n";
        throw FILESYSTEM_ERROR("Safe_Open_Output failed (tried to make text compressed)");}
    std::string actual_filename;
#ifndef COMPILE_WITHOUT_ZLIB_SUPPORT
    if(binary && write_compressed_if_possible){actual_filename=compressed?filename:filename+".gz";
        if(File_Writable(actual_filename))
            return Gzip_Out(actual_filename,flags);
    }
    else{actual_filename=compressed?Strip_Compression_Suffix(filename):filename;
        std::ostream* output=new std::ofstream(actual_filename.c_str(),flags);if(*output) return output;}
#else
    actual_filename=compressed?Strip_Compression_Suffix(filename):filename;
    std::ostream* output=new std::ofstream(actual_filename.c_str(),flags);if(*output) return output;
#endif
    LOG::cerr<<"Can't open "<<actual_filename<<" for write "<<(binary?"(binary)":"")<<std::endl;
    throw FILESYSTEM_ERROR("Safe_Open_Input failed");
}
//###################################################################
// Function File_Writable_Ignoring_Compression_Suffix
//###################################################################
bool File_Writable_Ignoring_Compression_Suffix(const std::string& filename)
{
    return !std::ofstream(filename.c_str(),std::ios::out).fail();
} // TODO: make this not create the file
//###################################################################
// Function Directory_Writable
//###################################################################
bool Directory_Writable(const std::string& dirname) // TODO: make this nicer
{
    static const char* dummy_filename="_PHYSBAM_FILE_UTILITIES_DUMMY_";
    std::string filename=dirname+"/"+dummy_filename;
    bool success=!std::ofstream(filename.c_str()).fail();
    remove(filename.c_str());
    return success;
}
//#####################################################################
std::string Get_File_Extension_Ignoring_Compression_Suffix(const std::string &filename)
{
    std::string::size_type lastperiod=filename.rfind('.'),lastslash=filename.rfind('/');
    if(lastperiod!=std::string::npos&&(lastslash==std::string::npos||lastperiod>lastslash))
        return filename.substr(lastperiod+1);
    return "";
}

std::string Get_Basename_Ignoring_Compression_Suffix(const std::string& filename)
{
    std::string::size_type lastperiod=filename.rfind(".");std::string::size_type lastslash=filename.rfind("/");
    if(lastperiod!=std::string::npos&&(lastslash==std::string::npos||lastperiod>lastslash))
        return filename.substr(0,lastperiod);
    return filename;
}

bool File_Extension_Matches_Ignoring_Compression_Suffix(const std::string& filename,const char* ext,int len_ext,const bool case_sensitive)
{
    int len=filename.length();
    const char* end=filename.c_str()+len;
    if(len<=len_ext+1) return false;
    if(len>4+len_ext && !Compare_Strings(end-3,3,".gz",3,case_sensitive)) end-=3;
    if(Compare_Strings(end-len_ext,len_ext,ext,len_ext,case_sensitive)) return false;
    return end[-len_ext-1]=='.';
}

bool File_Is_Compressed(const std::string& filename)
{
    int len=filename.length();
    const char* end=filename.c_str()+len;
    return len>4 && !Compare_Strings(end-3,3,".gz",3,true);
}

bool File_Exists(const std::string& filename)
{
    return fs::exists(filename) || (!File_Is_Compressed(filename) && fs::exists(filename+".gz"));
}

bool File_Writable(const std::string& filename)
{
    return File_Writable_Ignoring_Compression_Suffix(filename) || (!File_Is_Compressed(filename) && File_Writable_Ignoring_Compression_Suffix(filename+".gz"));
}

std::string Strip_Compression_Suffix(const std::string& filename)
{
    if(File_Is_Compressed(filename)) return Get_Basename_Ignoring_Compression_Suffix(filename);
    return filename;
}

std::string Get_File_Extension(const std::string &filename)
{
    return Get_File_Extension_Ignoring_Compression_Suffix(Strip_Compression_Suffix(filename));
}

std::string Get_Basename(const std::string& filename)
{
    return Get_Basename_Ignoring_Compression_Suffix(Strip_Compression_Suffix(filename));
}

void Ignore(std::istream& input,char c)
{
    while(!input.eof())
    {
        int i=input.peek();
        if(i==EOF) break;
        if(!isspace(i) && i!=c) break;
        input.get();
        if(i==c) break;
    }
}
}
