/*$
mlk in apdtool
Copyright (c) 2020 Azel

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
$*/

/*****************************************
 * mFile : ファイル関連操作
 *****************************************/

#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#include "mlk.h"
#include "mlk_file.h"
#include "mlk_charset.h"
#include "mlk_filestat.h"



/**@ 閉じる
 *
 * @g:mFile
 * 
 * @r:正常に閉じられたか */

mlkbool mFileClose(mFile file)
{
	if(file == MFILE_NONE)
		return TRUE;
	else
		return (close(file) == 0);
}

/**@ 読み込み用に開く
 *
 * @p:file 開いた mFile が格納される
 * @p:filename ファイル名 (UTF-8)
 * @r:成功したか */

mlkbool mFileOpen_read(mFile *file,const char *filename)
{
	char *str;
	int fd = MFILE_NONE;

	str = mUTF8toLocale(filename, -1, NULL);
	if(str)
	{
		fd = open(str, O_RDONLY);
		mFree(str);
	}

	*file = fd;

	return (fd != MFILE_NONE);
}

/**@ 新規書き込み用に開く
 *
 * @p:perm パーミッション。負の値で 0644。 */

mlkbool mFileOpen_write(mFile *file,const char *filename,int perm)
{
	char *str;
	int fd = MFILE_NONE;

	if(perm < 0) perm = 0644;

	str = mUTF8toLocale(filename, -1, NULL);
	if(str)
	{
		fd = open(str, O_CREAT | O_WRONLY | O_TRUNC, perm);
		mFree(str);
	}

	*file = fd;

	return (fd != MFILE_NONE);
}

/**@ ファイルサイズ取得 */

mlkfoff mFileGetSize(mFile file)
{
	struct stat st;

	if(fstat(file, &st) == 0)
		return st.st_size;
	else
		return 0;
}

/**@ 現在位置を取得
 *
 * @r:-1 でエラー */

mlkfoff mFileGetPos(mFile file)
{
	return lseek(file, 0, SEEK_CUR);
}

/**@ 先頭からの位置をセット
 *
 * @r:成功したか */

mlkbool mFileSetPos(mFile file,mlkfoff pos)
{
	return (lseek(file, pos, SEEK_SET) != -1);
}

/**@ カレント位置からシーク */

mlkbool mFileSeekCur(mFile file,mlkfoff seek)
{
	return (lseek(file, seek, SEEK_CUR) != -1);
}

/**@ 終端位置からシーク */

mlkbool mFileSeekEnd(mFile file,mlkfoff seek)
{
	return (lseek(file, seek, SEEK_END) != -1);
}


//=============================
// 読み込み
//=============================


/**@ 読み込み
 *
 * @r:実際に読み込んだサイズ。\
 * ※ size 以下の場合がある。\
 * ファイル終端時は 0。エラー時は -1。 */

int32_t mFileRead(mFile file,void *buf,int32_t size)
{
	ssize_t ret;

	do
	{
		ret = read(file, buf, size);
	} while(ret == -1 && errno == EINTR);

	return ret;
}

/**@ 指定サイズ分読み込み
 *
 * @d:エラー時を除き、指定サイズ分まで読み込む。
 *
 * @r:すべて読み込めたかどうか。\
 * エラー時や、サイズ分を読み込めないまま終端に来た場合は、FALSE。 */

mlkbool mFileRead_full(mFile file,void *buf,int32_t size)
{
	ssize_t ret;
	uint8_t *pd = (uint8_t *)buf;
	int32_t remain = size;

	while(remain > 0)
	{
		ret = read(file, pd, remain);

		if(ret == 0)
			//終端
			break;
		else if(ret == -1)
		{
			if(errno != EINTR) break;
		}
		else
		{
			pd += ret;
			remain -= ret;
		}
	}

	return (remain == 0);
}


//=============================
// 書き込み
//=============================


/**@ 書き込み
 *
 * @r:実際に書き込んだサイズ。\
 * ※ size 以下の場合がある。\
 * -1 でエラー。 */

int32_t mFileWrite(mFile file,const void *buf,int32_t size)
{
	ssize_t ret;

	do
	{
		ret = write(file, buf, size);
	} while(ret == -1 && errno == EINTR);

	return ret;
}

/**@ 指定サイズ分書き込み
 *
 * @d:エラー時を除き、指定サイズ分が書き込まれるまで繰り返す。
 *
 * @r:すべて書き込めたかどうか */

mlkbool mFileWrite_full(mFile file,const void *buf,int32_t size)
{
	ssize_t ret;
	const uint8_t *pd = (const uint8_t *)buf;
	int32_t remain = size;

	while(remain > 0)
	{
		ret = write(file, pd, remain);

		if(ret == -1)
		{
			if(errno != EINTR)
				break;
		}
		else
		{
			pd += ret;
			remain -= ret;
		}
	}

	return (remain == 0);
}


//========================
// ファイル操作
//========================


/* ファイルから情報取得 */

static mlkbool _get_filestat(const char *path,struct stat *dst)
{
	char *str;
	mlkbool ret;

	str = mUTF8toLocale(path, -1, NULL);
	if(!str) return FALSE;

	ret = (stat(str, dst) == 0);

	mFree(str);

	return ret;
}

/* struct stat -> mFileStat に変換 */

static void _conv_filestat(mFileStat *dst,struct stat *src)
{
	uint32_t f = 0;

	dst->perm = src->st_mode & 0777;
	dst->size = src->st_size;
	dst->time_access = src->st_atime;
	dst->time_modify = src->st_mtime;

	//フラグ

	if(S_ISREG(src->st_mode)) f |= MFILESTAT_F_NORMAL;
	if(S_ISDIR(src->st_mode)) f |= MFILESTAT_F_DIRECTORY;
	if(S_ISLNK(src->st_mode)) f |= MFILESTAT_F_SYMLINK;

	dst->flags = f;
}


/**@ 指定パスが存在するか
 *
 * @g:ファイル操作 */

mlkbool mIsExistPath(const char *path)
{
	struct stat st;

	return _get_filestat(path, &st);
}

/**@ 指定パスが存在し、通常ファイルかどうか */

mlkbool mIsExistFile(const char *path)
{
	struct stat st;

	if(!_get_filestat(path, &st))
		return FALSE;
	else
		return ((st.st_mode & S_IFREG) != 0);
}

/**@ 指定パスが存在し、ディレクトリかどうか */

mlkbool mIsExistDir(const char *path)
{
	struct stat st;

	if(!_get_filestat(path, &st))
		return FALSE;
	else
		return ((st.st_mode & S_IFDIR) != 0);
}

/**@ 指定パスの情報取得 */

mlkbool mGetFileStat(const char *path,mFileStat *dst)
{
	struct stat st;

	if(!_get_filestat(path, &st))
		return FALSE;
	else
	{
		_conv_filestat(dst, &st);
		return TRUE;
	}
}

/**@ ファイルサイズ取得 */

mlkbool mGetFileSize(const char *path,mlkfoff *dst)
{
	struct stat st;

	if(!_get_filestat(path, &st))
		return FALSE;
	else
	{
		*dst = st.st_size;
		return TRUE;
	}
}

/**@ ディレクトリ作成
 *
 * @p:perm パーミッション (負の値でデフォルト = 0755)
 * @r:0 で成功、-1 で失敗、1 ですでに存在している */

int mCreateDir(const char *path,int perm)
{
	char *str;
	int ret;

	if(perm < 0) perm = 0755;

	str = mUTF8toLocale(path, -1, NULL);
	if(!str) return -1;

	ret = mkdir(str, perm);

	mFree(str);

	//結果

	if(ret == 0)
		return 0;
	else
		return (errno == EEXIST)? 1: -1;
}

/**@ ディレクトリ作成 (上位の親も)
 *
 * @d:親のディレクトリが作成されていない場合、すべて作成する。
 * @r:0 で一つでも新たに作成した、-1 で失敗、1 で既にすべて存在している */

int mCreateDir_parents(const char *path,int perm)
{
	char *copy,*pc,*pc2,*pcend;

	//すでに存在しているか

	if(mIsExistPath(path)) return 1;

	//作業用にパスをコピー

	copy = mStrdup(path);
	if(!copy) return -1;

	//先頭から順に

	pc = copy;
	pcend = copy + strlen(copy);

	while(*pc)
	{
		//ディレクトリ名取得

		pc2 = strchr(pc + (*pc == '/'), '/');
		if(pc2)
			*pc2 = 0;
		else
			pc2 = pcend;

		//作成

		if(mCreateDir(copy, perm) == -1)
		{
			mFree(copy);
			return -1;
		}

		//

		if(pc2 == pcend) break;

		*pc2 = '/';
		pc = pc2 + 1;
	}

	mFree(copy);

	return 0;
}

/**@ ファイルを削除
 *
 * @r:FALSE で失敗 */

mlkbool mDeleteFile(const char *path)
{
	char *str;
	mlkbool ret;

	str = mUTF8toLocale(path, -1, NULL);
	if(!str) return FALSE;

	ret = (unlink(str) == 0);

	mFree(str);

	return ret;
}

/**@ ディレクトリを削除
 *
 * @d:{em:中身が空であること。:em} */

mlkbool mDeleteDir(const char *path)
{
	char *str;
	mlkbool ret;

	str = mUTF8toLocale(path, -1, NULL);
	if(!str) return FALSE;

	ret = (rmdir(str) == 0);

	mFree(str);

	return ret;
}
