/***************************************************************************
 *
 * path.cpp
 *
 * $Id: path.cpp 648752 2008-04-16 17:01:56Z faridz $
 *
 ***************************************************************************
 *
 * Licensed to the Apache Software  Foundation (ASF) under one or more
 * contributor  license agreements.  See  the NOTICE  file distributed
 * with  this  work  for  additional information  regarding  copyright
 * ownership.   The ASF  licenses this  file to  you under  the Apache
 * License, Version  2.0 (the  "License"); you may  not use  this file
 * except in  compliance with the License.   You may obtain  a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the  License is distributed on an  "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY  KIND, either  express or
 * implied.   See  the License  for  the  specific language  governing
 * permissions and limitations under the License.
 *
 * Copyright 1994-2006 Rogue Wave Software.
 * 
 **************************************************************************/

#ifndef _MSC_VER
#  ifdef __linux__
     // for symlink()
#    ifndef _XOPEN_SOURCE
#      define _XOPEN_SOURCE
#    endif
#    ifndef _XOPEN_SOURCE_EXTENDED
#      define _XOPEN_SOURCE_EXTENDED
#    endif
#  endif   // __linux__

#  include <unistd.h>      // for getcwd()
#  include <sys/stat.h>    // for struct stat, stat()
#else
#  include <direct.h>      // for struct _stat
#  include <io.h>
#  include <windows.h>     // for _getcwd()
#  include <sys/types.h>
#  include <sys/stat.h>    // for struct stat, stat()
#endif  // _MSC_VER


#include "path.h"

#include "diagnostic.h"   // for issue_diag()

#include <cassert>        // for assert()
#include <cerrno>         // for ERANGE
#include <cstdlib>        // for getenv()
#include <cstring>        // for strerror()
#include <string>         // for string


static char* get_cwd (char* s, std::size_t l)
{
#if !defined (_MSC_VER)
    return getcwd (s, l);
#else
    return _getcwd (s, l);
#endif
}


#ifndef _MSC_VER

void
create_symlink (const std::string &dir, 
                const std::string &xname, 
                const std::string &sname)
{
    int   ret  = 0;
    char* pret = 0;
    char  buf [1024];

    if (dir.size ()) {
        pret = get_cwd (buf, 1024);
        if (pret == 0) {
            issue_diag (213, true, 0, 
                        "could not retrieve current working directory\n") ;
        }
    
        ret = chdir (dir.c_str ());
        if (ret == -1)
            issue_diag (214, false, 0, "could not change directory to %s\n", 
                        dir.c_str ()) ;
    }

    ret = symlink(xname.c_str(), sname.c_str());

#ifdef EEXIST

    if (ret == -1 && errno != EEXIST) {
        issue_diag (215, false, 0,
                    "could not create symbolic link to %s "
                    "locale database (%s)\n", 
                    xname.c_str (), std::strerror (errno)) ;
    }

#endif   // EEXIST

    // go back to previous wd
    if (dir.size ()) {
        chdir (pret);
    }
}

#endif  // _MSC_VER


inline int
filemode (const char *path)
{
    assert (0 != path);

    struct stat buf;
    return stat (path, &buf) ? 0 : buf.st_mode;
}


std::string
get_pathname (const std::string &fname,
              const std::string &other /* = std::string () */)
{
    // use absolute path as given by fname
#if !defined (_MSC_VER)
    if (fname.size () && _RWSTD_PATH_SEP == fname [0]) 
#else
    if (fname.size () && 
        fname [1] && fname [1] == ':' && 
        fname [2] && fname [2] == _RWSTD_PATH_SEP) 
#endif
        return fname;

    std::string pathname;
    std::string dir_other (
        other.substr (0,other.rfind (_RWSTD_PATH_SEP) + 1));

    // OR use the path given through "other", be it relative or absolute
#if !defined (_MSC_VER)
    if (other.size () && _RWSTD_PATH_SEP == other [0]) {
#else
    if (other.size () && 
        other [1] && other [1] == ':' && 
        other [2] && other [2] == _RWSTD_PATH_SEP) {
#endif
        // other is an absolute path; compose dirname(other) + fname
        (pathname += dir_other) += fname;

    } else {
        pathname.resize (1024);

        // other is a relative path; compose cwd() + dirname(other) + fname
        while (!get_cwd (&pathname [0], pathname.size ())) {
            if (ERANGE != errno) {
                issue_diag (213, true, 0, 
                            "could not retrieve current working directory\n");
                return fname;
            }
            // buffer too small, reallocate
            pathname.resize (pathname.size () * 2);
        }
        
        pathname.resize (std::strlen (pathname.data ()));

        if (pathname.size() && 
            pathname [pathname.size () - 1] != _RWSTD_PATH_SEP)
            pathname += _RWSTD_PATH_SEP;

        (pathname += dir_other) += fname;
    }

#if !defined (_MSC_VER)
    if (S_ISREG (filemode (pathname.c_str ())))
#else
    if (S_IFREG & filemode (pathname.c_str ()))
#endif // !defined (_MSC_VER)
        return pathname;


    // use the value of RWSTD_SRC_ROOT
    const char* const src_root = std::getenv ("RWSTD_SRC_ROOT");
    if (src_root && *src_root) {
        // if the environment variable is not empty, append `fname'
        // to it and see if the file exists and names a regular file
        // if so, return its pathname
        pathname = src_root;
        (((pathname += _RWSTD_PATH_SEP) += "src") += _RWSTD_PATH_SEP) += fname;

#if !defined (_MSC_VER)
                if (S_ISREG (filemode (pathname.c_str ())))
#else
                if (S_IFREG & filemode (pathname.c_str ()))
#endif // !defined (_MSC_VER)
            return pathname;
    }

    // allocate a sufficiently large buffer for the cwd
    pathname.resize (1024);

    // use current working directory
    while (!get_cwd (&pathname [0], pathname.size ())) {

        if (ERANGE != errno) {
            issue_diag (213, true, 0, 
                        "could not retrieve current working directory\n");
            return fname;
        }

        // buffer too small, reallocate
        pathname.resize (pathname.size () * 2);
    }

    // chop off everything past the terminating NUL
    pathname.resize (std::strlen (pathname.data ()));
    pathname += fname;

#if !defined (_MSC_VER)
    if (S_ISREG (filemode (pathname.c_str ())))
#else
    if (S_IFREG & filemode (pathname.c_str ()))
#endif // !defined (_MSC_VER)
        return pathname;

    return fname;
}


int makedir (const char *name)
{
#if defined (_WIN32) || defined (_WIN64)

   if (   0 == CreateDirectory (name, NULL)
        && GetLastError () != ERROR_ALREADY_EXISTS) {

        LPVOID lpMsgBuf;

        FormatMessage (  FORMAT_MESSAGE_ALLOCATE_BUFFER
                       | FORMAT_MESSAGE_FROM_SYSTEM
                       | FORMAT_MESSAGE_IGNORE_INSERTS,
                       0,
                       GetLastError (),
                       MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
                       (LPTSTR)&lpMsgBuf,
                       0,
                       0);

        issue_diag (E_CALL, true, 0, (char*)lpMsgBuf);

        return -1;
    }
    
#else   // if !defined (_WIN{32,64})

   if (-1 == mkdir (name, 0755)) {

#  ifdef EEXIST

       if (errno != EEXIST) {
           issue_diag (E_CALL, true, 0, "failed to create directory %s: %s\n",
                       name, std::strerror (errno));

           return -1;
       }

#  endif   // EEXIST 

   }

#endif   // _WIN{32,64}

    return 0;
}
