U8 *FileExtDot(U8 *src)
{//Find dot char in name.
    I64 ch;

    while (ch = *src++)
        if (ch == '.' && *src != '/' && *src != '.')
            return src - 1;

    return NULL;
}

U8 *FileExtRemove(U8 *src, U8 *dst=NULL)
{//Remove filename extension from str.
    U8 *ptr;

    if (ptr = FileExtDot(src))
    {
        if (dst)
            StrCopy(dst, ptr + 1);
        *ptr = 0;
    }
    else if (dst)
        *dst = 0;

    return dst;
}

Bool IsDotC(U8 *filename)
{//Does name end in .C?
    I64 i = StrLen(filename);

    if (StrOcc(filename, '.') > 1 && filename[i - 1] == 'C' && filename[i - 2] == '.')
        return TRUE;
    else
        return FALSE;
}

Bool FilesFindMatch(U8 *_test_name,U8 *files_find_mask,I64 fuf_flags=0)
{//Does filename meet Files Find mask?
    I64  tn_len = StrLen(_test_name), mask_len = StrLen(files_find_mask);
    U8  *mask1  = MAlloc(mask_len + 1), *mask2 = MAlloc(mask_len + 1), *ptr, *test_name1, *test_name2;
    Bool res    = FALSE;

    StrCopy(mask1,files_find_mask);
    if (StrOcc(_test_name, '/'))
    {
        test_name1 = MAlloc(tn_len + 1);
        test_name2 = MAlloc(tn_len + 1);
        StrCopy(test_name1, _test_name);
        StrLastRemove(test_name1, "/", test_name2);
    }
    else
    {
        test_name1 = NULL;
        test_name2 = NULL;
    }
    while (TRUE)
    {
        StrFirstRemove(mask1, ";", mask2);
        if (!test_name2 || StrOcc(mask2, '/'))
            ptr = _test_name;
        else
            ptr = test_name2;
        if (*mask2)
        {
            if (*mask2 == '!')
            {
                if (WildMatch(ptr, mask2 + 1))
                {
                    res = FALSE;
                    break;
                }
            }
            else
            {
                if (WildMatch(ptr, mask2))
                {
                    if (Bt(&fuf_flags, FUf_JUST_TXT) && !FilesFindMatch(_test_name, FILEMASK_TXT)) {
                        res = FALSE;
                        break;
                    }
                    else if (Bt(&fuf_flags, FUf_JUST_DD) && !FilesFindMatch(_test_name, FILEMASK_DD)) {
                        res = FALSE;
                        break;
                    }
                    else if (Bt(&fuf_flags, FUf_JUST_SRC) && !FilesFindMatch(_test_name, FILEMASK_SRC)) {
                        res = FALSE;
                        break;
                    }
                    else if (Bt(&fuf_flags, FUf_JUST_AOT) && !FilesFindMatch(_test_name, FILEMASK_AOT)) {
                        res = FALSE;
                        break;
                    }
                    else if (Bt(&fuf_flags, FUf_JUST_JIT) && !FilesFindMatch(_test_name, FILEMASK_JIT)) {
                        res = FALSE;
                        break;
                    }
                    else if (Bt(&fuf_flags, FUf_JUST_GR) && !FilesFindMatch(_test_name, FILEMASK_GR)) {
                        res = FALSE;
                        break;
                    }
                    else
                        res = TRUE;
                }
            }
        }
        else
            break;
    }
    Free(test_name1);
    Free(test_name2);
    Free(mask1);
    Free(mask2);

    return res;
}

U8 *DirNameAbs(U8 *_dirname)
{//MAlloc absolute dir string with drive letter.
    I64 maxlen;
    U8  drv[3], *res, *buf, *buf2, *buf3, *buf4, *dirname, *free_dirname;

    if (!Fs->cur_dir || !*Fs->cur_dir)
        return StrNew(_dirname);
    free_dirname=dirname = MStrUtil(_dirname, SUF_REM_LEADING | SUF_REM_TRAILING | SUF_REM_CTRL_CHARS);
    *drv = Drive2Letter;
    drv[1] = ':';
    drv[2] = 0;
    if (*dirname && dirname[1] == ':')
    {
        if (*dirname == ':')
            *drv = blkdev.boot_drive_let;
        else if (*dirname == '~')
            *drv = *blkdev.home_dir;
        else
            *drv = *dirname;
        dirname = dirname + 2;
        buf = StrNew("/");
    }
    else
        buf = StrNew(Fs->cur_dir);
    if (*dirname == '/')
    {
        Free(buf);
        buf = StrNew("/");
        dirname++;
    }
    buf2    = StrNew(dirname);
    maxlen  = StrLen(buf) + 1 + StrLen(buf2) + 1;
    buf3    = MAlloc(maxlen);
    buf4    = MAlloc(maxlen);
    StrCopy(buf3, buf);
    while (*buf2)
    {
        StrFirstRemove(buf2, "/", buf4);
        if (!*buf4)
            StrCopy(buf3, "/");
        else if (!StrCompare(buf4, ".."))
        {
            StrLastRemove(buf3, "/");
            if (!*buf3)
                StrCopy(buf3, "/");
        }
        else if (!StrCompare(buf4, "~"))
        {
            Free(buf3);
            buf3 = MAlloc(StrLen(blkdev.home_dir + 2) + 1 + StrLen(buf2) + 1);
            StrCopy(buf3, blkdev.home_dir + 2);
            *drv = *blkdev.home_dir;
        }
        else if (!StrCompare(buf4, "."));
        else if (*buf4)
        {
            if (StrCompare(buf3, "/"))
                CatPrint(buf3, "/");
            CatPrint(buf3, buf4);
        }
    }
    Free(buf);
    res = MAlloc(StrLen(buf3) + 3);
    StrCopy(res, drv);
    StrCopy(res + 2, buf3);
    Free(buf2);
    Free(buf3);
    Free(buf4);
    Free(free_dirname);

    return res;
}

U8 *FileNameAbs(U8 *_filename, I64 fuf_flags=NONE)
{//Absolute filename. Accepts FUF_SCAN_PARENTS.
    U8          *res, *filename, *buf, *buf_file, *buf_dir, *free_filename, *free_buf;
    CDirEntry    de;

    free_filename = filename = MStrUtil(_filename, SUF_REM_LEADING | SUF_REM_TRAILING | SUF_REM_CTRL_CHARS);
    free_buf = buf = StrNew(filename);
    if (*buf && buf[1] == ':')
    {
        buf += 2;
        filename += 2;
    }
    buf_file = MAlloc(StrLen(free_filename) + 1);
    StrLastRemove(buf, "/", buf_file);
    if (*filename == '/' && !*buf)
        StrCopy(buf, "/");
    buf_dir = DirNameAbs(free_buf);
    Free(free_buf);
    res = MAlloc(StrLen(buf_dir) + 1 + StrLen(buf_file) + 1);
    StrCopy(res, buf_dir);
    if (res[StrLen(res) - 1] != '/')
        CatPrint(res, "/");
    CatPrint(res, buf_file);
    Free(buf_file);
    Free(buf_dir);
    Free(free_filename);
    if (fuf_flags && FileFind(res, &de, fuf_flags | FUF_JUST_FILES))
    {
        Free(res);
        res = de.full_name;
    }

    return res;
}

U8 *ExtChange(U8 *filename, U8 *extension)
{//Change filename extension.
    U8 *res = MAlloc(StrLen(filename) + 1 + StrLen(extension) + 1);

    StrCopy(res, filename);
    if (FileExtDot(filename))
        FileExtRemove(res);

    return CatPrint(res, ".%s", extension);
}

U8 *ExtDefault(U8 *filename, U8 *extension)
{//Give extension if has none.
    U8 *res = MAlloc(StrLen(filename) + 1 + StrLen(extension) + 1);

    StrCopy(res, filename);
    if (!FileExtDot(filename))
        CatPrint(res, ".%s", extension);

    return res;
}

CDirEntry *Cd2DirEntry(CDirEntry *tmpde, U8 *abs_name)
{
    I64 i;

    while (tmpde)
    {
        i = StrLen(tmpde->full_name);
        if (StrNCompare(tmpde->full_name, abs_name, i) ||
                i && tmpde->full_name[i - 1] != '/' &&
                abs_name[i] &&
                abs_name[i] != '/')
            tmpde = tmpde->next;
        else
            if (StrLen(abs_name) == i)
                return tmpde;
            else
                return Cd2DirEntry(tmpde->sub, abs_name);
    }

    return NULL;
}

I64 FileAttr(U8 *name, I64 attr=0)
{
    if (IsDotC(name))
        attr |= RS_ATTR_CONTIGUOUS;

    return attr;
}

Bool FileNameCheck(U8 *filename)
{//Return check for valid filename, not checking existence.
    U8 *ptr = filename;

    if (!filename)
        return FALSE;
    if (!*ptr)
        return FALSE;
    if (*ptr == '.')
    {
        if (!ptr[1])
            return TRUE;
        if (ptr[1] == '.' && !ptr[2])
            return TRUE;
    }
    if (StrLen(filename) >= CDIR_FILENAME_LEN)
        return FALSE;
    while (*ptr)
        if (!Bt(char_bmp_filename, *ptr++))
            return FALSE;

    return TRUE;
}

U8 *FileNameTmpTxt()
{//Make pretty-safe tmp filename in home dir.
    return MStrPrint("~/SysTmp%X.DD", TSCGet >> 8 & 0xFFFFFFFF);
}

U8 *DirCur(CTask *task=NULL, CTask *mem_task=NULL)
{//MAlloc copy of cur dir with drive letter.
    U8 *st;

    if (!task)
        task = Fs;
    if (!task->cur_dir)
        return NULL;
    st = MAlloc(StrLen(task->cur_dir) + 3, mem_task);
    *st = Drive2Letter(task->cur_dv);
    st[1] = ':';
    StrCopy(st + 2, task->cur_dir);

    return st;
}

U8 *DirFile(U8 *dirname, U8 *name=NULL, U8 *_extension=NULL)
{/*Strips file from dirname, scans for file upward until found or
returns default.

("/Kernel/KHashA.ZC",NULL,NULL) returns "D:/Kernel"
("/Kernel",NULL,"PRJ")                  returns "D:/Kernel/Kernel.PRJ"
("/Kernel/BlkDev",NULL,"PRJ")                   returns "D:/Kernel/Kernel.PRJ"
("/Apps/Psalmody","Load","ZC")  returns "D:/Apps/Psalmody/Load.ZC"
*/
    U8 *st = DirNameAbs(dirname), *st2, *st3, *res, *default = NULL, *ext;

    if (_extension && *_extension)
    {
        if (*_extension == '.')
            ext = StrNew(_extension);
        else
            ext = MStrPrint(".%s", _extension);
    }
    else
        ext = StrNew("");
    while (StrOcc(st, '/') && !IsDir(st))
        StrLastRemove(st, "/");
    while (StrOcc(st, '/'))
    {
        st2 = StrNew(st);
        st3 = StrNew(st);
        StrLastRemove(st2, "/", st3);

        if (name)
            res = MStrPrint("%s/%s%s", st, name, ext);
        else
        {
            if (*ext)
                res = MStrPrint("%s/%s%s", st, st3, ext);
            else
                res = StrNew(st);
        }
        if (!default)
            default = StrNew(res);
        if (!*ext && (!name || !*name) || FileFind(res))
        {
            Free(st3);
            Free(st2);
            Free(st);
            Free(default);
            Free(ext);
            return res;
        }
        Free(st);
        st = st2;
        Free(st3);
    }
    Free(st);
    Free(ext);

    return default;
}