/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Filename manipulations.  Includes some basic file ops (e.g. tests for
 * file existence) as well.
 */
#ifndef UTIL_PATHNAME_H
#define UTIL_PATHNAME_H

/*
 * Holds a full or partial pathname, manipulating it in various ways.
 *
 * This creates some hefty buffers for _splitpath() to use, so don't use
 * these as storage within other objects.
 *
 * Functions that return CStrings should have their values assigned into
 * CString variables.  Attempting to set a "const char*" to them can cause
 * problems as the CString being returned is in local storage.
 */
class PathName {
public:
    PathName(const WCHAR* pathName = L"", WCHAR fssep = '\\') {
        ASSERT(fssep == '\\');  // not handling other cases yet
        fPathName = pathName;
        fFssep = fssep;
        fSplit = false;
    }
    PathName(const CString& pathName, WCHAR fssep = '\\') {
        ASSERT(fssep == '\\');  // not handling other cases yet
        fPathName = pathName;
        fFssep = fssep;
        fSplit = false;
    }
    ~PathName(void) {}

    /*
     * Name manipulations.
     */
    void SetPathName(const WCHAR* pathName, WCHAR fssep = '\\') {
        ASSERT(fssep == '\\');  // not handling other cases yet
        fPathName = pathName;
        fFssep = fssep;
        fSplit = false;
    }
    void SetPathName(const CString& pathName, WCHAR fssep = '\\') {
        ASSERT(fssep == '\\');  // not handling other cases yet
        fPathName = pathName;
        fFssep = fssep;
        fSplit = false;
    }

    // get the full pathname we have stored
    CString GetPathName(void) const { return fPathName; }

    // create a pathname from a "foreign" OS name
    int ConvertFrom(const char* foreignName, char foreignFssep);

    // return just the filename: "C:\foo\bar.txt" -> "bar.txt"
    CString GetFileName(void);

    // return just the extension: C:\foo\bar.txt --> ".txt"
    CString GetExtension(void);

    // return just the drive component: "C:\foo\bar.txt" --> "C:"
    CString GetDriveOnly(void);

    // return drive and path component: "C:\foo\bar.txt" -> "C:\foo\"
    // (assumes trailing paths end in '\')
    CString GetDriveAndPath(void);
    CString GetPathOnly(void);

    /*
     * Expand the short file name of an existing file into its long form.
     *
     * Returns 0 on success, -1 on failure.
     */
    int SFNToLFN(void);

    // returns the description of the file type (as seen in explorer)
    CString GetDescription(void);

    // determine whether or not the file exists
    bool Exists(void);

    // check the status of a file
    int CheckFileStatus(struct _stat* psb, bool* pExists, bool* pIsReadable,
        bool* pIsDir);

    // get the modification date
    time_t GetModWhen(void);
    // set the modification date
    int SetModWhen(time_t when);

    /*
     * Create subdirectories, if needed.  The paths leading up to the filename
     * in "pathname" will be created.
     *
     * If "pathname" is just a filename, or the set of directories matches
     * the last directory we created, we don't do anything.
     *
     * Returns 0 on success, or a Windows error code on failure.
     */
    int CreatePathIFN(void);

    /*
     * Return the filename extension found in a full pathname.
     *
     * An extension is the stuff following the last '.' in the filename.  If
     * there is nothing following the last '.', then there is no extension.
     *
     * Returns a pointer to the '.' preceding the extension, or NULL if no
     * extension was found.
     *
     * We guarantee that there is at least one character after the '.'.
     */
    static const WCHAR* FindExtension(const WCHAR* pathname, WCHAR fssep);

    /*
     * Find the filename component of a local pathname.  Uses the fssep passed
     * in.  If the fssep is '\0' (as is the case for DOS 3.3), then the entire
     * pathname is returned.
     *
     * Always returns a pointer to a string; never returns NULL.
     */
    static const WCHAR* FilenameOnly(const WCHAR* pathname, WCHAR fssep);

    /*
     * Test to see if a wide-to-narrow filename conversion failed.
     *
     * Returns true if all is well, false with *pErrMsg set if something
     * went wrong.
     */
    static bool TestNarrowConversion(const CString& original,
            const CStringA& converted, CString* pErrMsg) {
        int index = converted.ReverseFind('?');
        if (index < 0) {
            // no '?' is good
        } else if (index == 2 && converted.Left(4) == "\\\\?\\") {
            // Win32 file namespace path strings start with "\\?\".  If that's
            // the first occurrence of '?', we're still good.
        } else {
            // This is most likely the result of a failed wide-to-narrow
            // string conversion.
            pErrMsg->Format(L"Unable to open '%ls' -- Unicode filename "
                            L"conversion is invalid ('%hs')",
                    (LPCWSTR) original, (LPCSTR) converted);
            return false;
        }
        return true;
    }
private:
    DECLARE_COPY_AND_OPEQ(PathName)

    void SplitIFN(void) {
        if (!fSplit) {
            _wsplitpath(fPathName, fDrive, fDir, fFileName, fExt);
            fSplit = true;
        }
    }

    /*
     * Invoke the system-dependent directory creation function.
     */
    int Mkdir(const WCHAR* dir);

    /*
     * Determine if a file exists, and if so whether or not it's a directory.
     *
     * Set fields you're not interested in to NULL.
     *
     * On success, returns 0 and fields are set appropriately.  On failure,
     * returns nonzero and result values are undefined.
     */
    int GetFileInfo(const WCHAR* pathname, struct _stat* psb, time_t* pModWhen,
        bool* pExists, bool* pIsReadable, bool* pIsDirectory);

    /*
     * Create a single subdirectory if it doesn't exist.  If the next-highest
     * subdirectory level doesn't exist either, cut down the pathname and
     * recurse.
     *
     * "pathEnd" points at the last valid character.  The length of the valid
     * path component is therefore (pathEnd-pathStart+1).
     */
    int CreateSubdirIFN(const WCHAR* pathStart, const WCHAR* pathEnd,
        WCHAR fssep);

    CString     fPathName;
    WCHAR       fFssep;
    bool        fSplit;

    WCHAR       fDrive[_MAX_DRIVE];         // 3
    WCHAR       fDir[_MAX_DIR];             // 256
    WCHAR       fFileName[_MAX_FNAME];      // 256
    WCHAR       fExt[_MAX_EXT];             // 256
};

#endif /*UTIL_PATHNAME_H*/
