/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * DiskImgLib global utility functions.
 */
#include "StdAfx.h"
#include "DiskImgPriv.h"

#define kFilenameExtDelim   '.'     /* separates extension from filename */

/*
 * Get values from a memory buffer.
 */
uint16_t DiskImgLib::GetShortLE(const uint8_t* ptr)
{
    return *ptr | (uint16_t) *(ptr+1) << 8;
}

uint32_t DiskImgLib::GetLongLE(const uint8_t* ptr)
{
    return *ptr |
            (uint32_t) *(ptr+1) << 8 |
            (uint32_t) *(ptr+2) << 16 |
            (uint32_t) *(ptr+3) << 24;
}

uint16_t DiskImgLib::GetShortBE(const uint8_t* ptr)
{
    return *(ptr+1) | (uint16_t) *ptr << 8;
}

uint32_t DiskImgLib::GetLongBE(const uint8_t* ptr)
{
    return *(ptr+3) |
            (uint32_t) *(ptr+2) << 8 |
            (uint32_t) *(ptr+1) << 16 |
            (uint32_t) *ptr << 24;
}

uint32_t DiskImgLib::Get24BE(const uint8_t* ptr)
{
    return *(ptr+2) |
            (uint32_t) *(ptr+1) << 8 |
            (uint32_t) *ptr << 16;
}

void DiskImgLib::PutShortLE(uint8_t* ptr, uint16_t val)
{
    *ptr++ = (uint8_t) val;
    *ptr = val >> 8;
}

void DiskImgLib::PutLongLE(uint8_t* ptr, uint32_t val)
{
    *ptr++ = (uint8_t) val;
    *ptr++ = (uint8_t) (val >> 8);
    *ptr++ = (uint8_t) (val >> 16);
    *ptr = (uint8_t) (val >> 24);
}

void DiskImgLib::PutShortBE(uint8_t* ptr, uint16_t val)
{
    *ptr++ = val >> 8;
    *ptr = (uint8_t) val;
}

void DiskImgLib::PutLongBE(uint8_t* ptr, uint32_t val)
{
    *ptr++ = (uint8_t) (val >> 24);
    *ptr++ = (uint8_t) (val >> 16);
    *ptr++ = (uint8_t) (val >> 8);
    *ptr = (uint8_t) val;
}


/*
 * Read a two-byte little-endian value.
 */
DIError DiskImgLib::ReadShortLE(GenericFD* pGFD, uint16_t* pBuf)
{
    DIError dierr;
    uint8_t val[2];

    dierr = pGFD->Read(&val[0], 1);
    if (dierr == kDIErrNone)
        dierr = pGFD->Read(&val[1], 1);

    *pBuf = val[0] | (short) val[1] << 8;
    return dierr;
}

/*
 * Read a four-byte little-endian value.
 */
DIError DiskImgLib::ReadLongLE(GenericFD* pGFD, uint32_t* pBuf)
{
    DIError dierr;
    uint8_t val[4];

    dierr = pGFD->Read(&val[0], 1);
    if (dierr == kDIErrNone)
        dierr = pGFD->Read(&val[1], 1);
    if (dierr == kDIErrNone)
        dierr = pGFD->Read(&val[2], 1);
    if (dierr == kDIErrNone)
        dierr = pGFD->Read(&val[3], 1);

    *pBuf = val[0] | (uint32_t)val[1] << 8 |
        (uint32_t)val[2] << 16 | (uint32_t)val[3] << 24;
    return dierr;
}

/*
 * Write a two-byte little-endian value.
 */
DIError DiskImgLib::WriteShortLE(FILE* fp, uint16_t val)
{
    putc(val, fp);
    putc(val >> 8, fp);
    return kDIErrNone;
}

/*
 * Write a four-byte little-endian value.
 */
DIError DiskImgLib::WriteLongLE(FILE* fp, uint32_t val)
{
    putc(val, fp);
    putc(val >> 8, fp);
    putc(val >> 16, fp);
    putc(val >> 24, fp);
    return kDIErrNone;
}

/*
 * Write a two-byte little-endian value.
 */
DIError DiskImgLib::WriteShortLE(GenericFD* pGFD, uint16_t val)
{
    uint8_t buf;

    buf = (uint8_t) val;
    pGFD->Write(&buf, 1);
    buf = val >> 8;
    return pGFD->Write(&buf, 1);
}

/*
 * Write a four-byte little-endian value.
 */
DIError DiskImgLib::WriteLongLE(GenericFD* pGFD, uint32_t val)
{
    uint8_t buf;

    buf = (uint8_t) val;
    pGFD->Write(&buf, 1);
    buf = (uint8_t) (val >> 8);
    pGFD->Write(&buf, 1);
    buf = (uint8_t) (val >> 16);
    pGFD->Write(&buf, 1);
    buf = (uint8_t) (val >> 24);
    return pGFD->Write(&buf, 1);
}

/*
 * Write a two-byte big-endian value.
 */
DIError DiskImgLib::WriteShortBE(GenericFD* pGFD, uint16_t val)
{
    uint8_t buf;

    buf = val >> 8;
    pGFD->Write(&buf, 1);
    buf = (uint8_t) val;
    return pGFD->Write(&buf, 1);
}

/*
 * Write a four-byte big-endian value.
 */
DIError DiskImgLib::WriteLongBE(GenericFD* pGFD, uint32_t val)
{
    uint8_t buf;

    buf = (uint8_t) (val >> 24);
    pGFD->Write(&buf, 1);
    buf = (uint8_t) (val >> 16);
    pGFD->Write(&buf, 1);
    buf = (uint8_t) (val >> 8);
    pGFD->Write(&buf, 1);
    buf = (uint8_t) val;
    return pGFD->Write(&buf, 1);
}


/*
 * 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.
 */
const char* DiskImgLib::FilenameOnly(const char* pathname, char fssep)
{
    const char* retstr;
    const char* pSlash;
    char* tmpStr = NULL;

    assert(pathname != NULL);
    if (fssep == '\0') {
        retstr = pathname;
        goto bail;
    }

    pSlash = strrchr(pathname, fssep);
    if (pSlash == NULL) {
        retstr = pathname;      /* whole thing is the filename */
        goto bail;
    }

    pSlash++;
    if (*pSlash == '\0') {
        if (strlen(pathname) < 2) {
            retstr = pathname;  /* the pathname is just "/"?  Whatever */
            goto bail;
        }

        /* some bonehead put an fssep on the very end; back up before it */
        /* (not efficient, but this should be rare, and I'm feeling lazy) */
        tmpStr = strdup(pathname);
        tmpStr[strlen(pathname)-1] = '\0';
        pSlash = strrchr(tmpStr, fssep);

        if (pSlash == NULL) {
            retstr = pathname;  /* just a filename with a '/' after it */
            goto bail;
        }

        pSlash++;
        if (*pSlash == '\0') {
            retstr = pathname;  /* I give up! */
            goto bail;
        }

        retstr = pathname + (pSlash - tmpStr);

    } else {
        retstr = pSlash;
    }

bail:
    free(tmpStr);
    return retstr;
}

/*
 * 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 '.'.
 */
const char* DiskImgLib::FindExtension(const char* pathname, char fssep)
{
    const char* pFilename;
    const char* pExt;

    /*
     * We have to isolate the filename so that we don't get excited
     * about "/foo.bar/file".
     */
    pFilename = FilenameOnly(pathname, fssep);
    assert(pFilename != NULL);
    pExt = strrchr(pFilename, kFilenameExtDelim);

    /* also check for "/blah/foo.", which doesn't count */
    if (pExt != NULL && *(pExt+1) != '\0')
        return pExt;

    return NULL;
}

/*
 * Like strcpy(), but allocate with new[] instead.
 *
 * If "str" is NULL, or "new" fails, this returns NULL.
 *
 * TODO: should be "StrdupNew()"
 */
char* DiskImgLib::StrcpyNew(const char* str)
{
    char* newStr;

    if (str == NULL)
        return NULL;
    newStr = new char[strlen(str)+1];
    if (newStr != NULL)
        strcpy(newStr, str);
    return newStr;
}


#ifdef _WIN32
/*
 * Convert the value from GetLastError() to its DIError counterpart.
 */
DIError DiskImgLib::LastErrorToDIError(void)
{
    DWORD lastErr = ::GetLastError();

    switch (lastErr) {
    case ERROR_FILE_NOT_FOUND:      return kDIErrFileNotFound;      // 2
    case ERROR_ACCESS_DENIED:       return kDIErrAccessDenied;      // 5
    case ERROR_WRITE_PROTECT:       return kDIErrWriteProtected;    // 19
    case ERROR_SECTOR_NOT_FOUND:    return kDIErrGeneric;           // 27
    case ERROR_SHARING_VIOLATION:   return kDIErrSharingViolation;  // 32
    case ERROR_HANDLE_EOF:          return kDIErrEOF;               // 38
    case ERROR_INVALID_PARAMETER:   return kDIErrInvalidArg;        // 87
    case ERROR_SEM_TIMEOUT:         return kDIErrGenericIO;         // 121
        // ERROR_SEM_TIMEOUT seen read bad blocks from floptical under Win2K

    case ERROR_INVALID_HANDLE:                                      // 6
        LOGI("HEY: got ERROR_INVALID_HANDLE!");
        return kDIErrInternal;
    case ERROR_NEGATIVE_SEEK:                                       // 131
        LOGI("HEY: got ERROR_NEGATIVE_SEEK!");
        return kDIErrInternal;
    default:
        LOGI("LastErrorToDIError: not converting 0x%08lx (%ld)",
            lastErr, lastErr);
        return kDIErrGeneric;
    }
}

/*
 * Returns "true" if we're running on Win9x (Win95, Win98, WinME), "false"
 * if not (could be WinNT/2K/XP or even Win31 with Win32s).
 */
bool DiskImgLib::IsWin9x(void)
{
    return false;
}
#endif
