#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <libgen.h>

#include "fallback.h"
#include "util.h"

#include "common.h"
#include "env.h"
#include "wutil.h"
#include "halloc.h"
#include "halloc_util.h"
#include "path.h"
#include "expand.h"

/**
   Unexpected error in path_get_path()
*/
#define MISSING_COMMAND_ERR_MSG _( L"Error while searching for command '%ls'" )

bool path_get_path_string(const wcstring &cmd_str, wcstring &output, const env_vars &vars)
{
    const wchar_t * const cmd = cmd_str.c_str();
    int err = ENOENT;
    debug( 3, L"path_get_path_string( '%ls' )", cmd );
    
	if(wcschr( cmd, L'/' ) != 0 )
	{
		if( waccess( cmd, X_OK )==0 )
		{
			struct stat buff;
			if(wstat( cmd, &buff ))
			{
				return false;
			}
			
			if (S_ISREG(buff.st_mode))
            {
                output = cmd_str;
                return true;
            }
            else
			{
				errno = EACCES;
				return false;
			}
		}
		else
		{
			//struct stat buff;
			//wstat( cmd, &buff );
			return false;
		}
		
	}
	else
	{
		const wchar_t *path = vars.get(L"PATH");
		if( path == 0 )
		{
			if( contains( PREFIX L"/bin", L"/bin", L"/usr/bin" ) )
			{
				path = L"/bin" ARRAY_SEP_STR L"/usr/bin";
			}
			else
			{
				path = L"/bin" ARRAY_SEP_STR L"/usr/bin" ARRAY_SEP_STR PREFIX L"/bin";
			}
		}
		
        wcstokenizer tokenizer(path, ARRAY_SEP_STR);
        wcstring new_cmd;
        while (tokenizer.next(new_cmd))
        {
            size_t path_len = new_cmd.size();
            if (path_len == 0) continue;
            
            append_path_component(new_cmd, cmd_str);
			if( waccess( new_cmd.c_str(), X_OK )==0 )
			{
				struct stat buff;
				if( wstat( new_cmd.c_str(), &buff )==-1 )
				{
					if( errno != EACCES )
					{
						wperror( L"stat" );
					}
					continue;
				}
				if( S_ISREG(buff.st_mode) )
				{
					output = new_cmd;
                    return true;
				}
				err = EACCES;
				
			}
			else
			{
				switch( errno )
				{
					case ENOENT:
					case ENAMETOOLONG:
					case EACCES:
					case ENOTDIR:
						break;
					default:
					{
						debug( 1,
                              MISSING_COMMAND_ERR_MSG,
                              new_cmd.c_str() );
						wperror( L"access" );
					}
				}
			}
		}
	}
    
	errno = err;
	return false;

}

wchar_t *path_get_path( const wchar_t *cmd )
{
	int err = ENOENT;
	
	CHECK( cmd, 0 );

	debug( 3, L"path_get_path( '%ls' )", cmd );

	if(wcschr( cmd, L'/' ) != 0 )
	{
		if( waccess( cmd, X_OK )==0 )
		{
			struct stat buff;
			if(wstat( cmd, &buff ))
			{
				return 0;
			}
			
			if( S_ISREG(buff.st_mode) )
				return wcsdup( cmd );
			else
			{
				errno = EACCES;
				return 0;
			}
		}
		else
		{
			struct stat buff;
			wstat( cmd, &buff );
			return 0;
		}
		
	}
	else
	{
		env_var_t path = env_get_string(L"PATH");
		if( path.missing() )
		{
			if( contains( PREFIX L"/bin", L"/bin", L"/usr/bin" ) )
			{
				path = L"/bin" ARRAY_SEP_STR L"/usr/bin";
			}
			else
			{
				path = L"/bin" ARRAY_SEP_STR L"/usr/bin" ARRAY_SEP_STR PREFIX L"/bin";
			}
		}
		
		/*
		  Allocate string long enough to hold the whole command
		*/
		wchar_t *new_cmd = (wchar_t *)calloc(wcslen(cmd)+path.size()+2, sizeof(wchar_t) );
		
		/*
		  We tokenize a copy of the path, since strtok modifies
		  its arguments
		*/
		wchar_t *path_cpy = wcsdup( path.c_str() );
		const wchar_t *nxt_path = path.c_str();
		wchar_t *state;
			
		if( (new_cmd==0) || (path_cpy==0) )
		{
			DIE_MEM();
		}

		for( nxt_path = wcstok( path_cpy, ARRAY_SEP_STR, &state );
			 nxt_path != 0;
			 nxt_path = wcstok( 0, ARRAY_SEP_STR, &state) )
		{
			int path_len = wcslen( nxt_path );
			wcscpy( new_cmd, nxt_path );
			if( new_cmd[path_len-1] != L'/' )
			{
				new_cmd[path_len++]=L'/';
			}
			wcscpy( &new_cmd[path_len], cmd );
			if( waccess( new_cmd, X_OK )==0 )
			{
				struct stat buff;
				if( wstat( new_cmd, &buff )==-1 )
				{
					if( errno != EACCES )
					{
						wperror( L"stat" );
					}
					continue;
				}
				if( S_ISREG(buff.st_mode) )
				{
					free( path_cpy );
					return new_cmd;
				}
				err = EACCES;
				
			}
			else
			{
				switch( errno )
				{
					case ENOENT:
					case ENAMETOOLONG:
					case EACCES:
					case ENOTDIR:
						break;
					default:
					{
						debug( 1,
							   MISSING_COMMAND_ERR_MSG,
							   new_cmd );
						wperror( L"access" );
					}
				}
			}
		}
		free( path_cpy );

	}

	errno = err;
	return 0;
}


bool path_get_cdpath_string(const wcstring &dir_str, wcstring &result, const env_vars &vars)
{
	wchar_t *res = 0;
	int err = ENOENT;
    bool success = false;
    
    const wchar_t *const dir = dir_str.c_str();
	if( dir[0] == L'/'|| (wcsncmp( dir, L"./", 2 )==0) )
	{
		struct stat buf;
		if( wstat( dir, &buf ) == 0 )
		{
			if( S_ISDIR(buf.st_mode) )
			{
				result = dir_str;
                success = true;
			}
			else
			{
				err = ENOTDIR;
			}
            
		}
	}
	else
	{
		const wchar_t *path = vars.get(L"CDPATH");
		if( !path || !wcslen(path) )
		{
			path = L".";
		}
                
        wcstokenizer tokenizer(path, ARRAY_SEP_STR);
        wcstring next_path;
        while (tokenizer.next(next_path))
        {
            expand_tilde(next_path);
            if (next_path.size() == 0) continue;
            
            wcstring whole_path = next_path;
            append_path_component(whole_path, dir);
            
			struct stat buf;
			if( wstat( whole_path.c_str(), &buf ) == 0 )
			{
				if( S_ISDIR(buf.st_mode) )
				{
                    result = whole_path;
                    success = true;
					break;
				}
				else
				{
					err = ENOTDIR;
				}
			}
			else
			{
				if( lwstat( whole_path.c_str(), &buf ) == 0 )
				{
					err = EROTTEN;
				}
			}
        }
    }
		
    
	if( !success )
	{
		errno = err;
	}
    
	return res;
}

wchar_t *path_allocate_cdpath( const wchar_t *dir )
{
	wchar_t *res = 0;
	int err = ENOENT;
	if( !dir )
		return 0;


	if( dir[0] == L'/'|| (wcsncmp( dir, L"./", 2 )==0) )
	{
		struct stat buf;
		if( wstat( dir, &buf ) == 0 )
		{
			if( S_ISDIR(buf.st_mode) )
			{
				res = wcsdup(dir);
			}
			else
			{
				err = ENOTDIR;
			}

		}
	}
	else
	{
		wchar_t *path_cpy;
		const wchar_t *nxt_path;
		wchar_t *state;
		wchar_t *whole_path;

		env_var_t path = env_get_string(L"CDPATH");
		if( path.missing_or_empty() )
			path = L".";

		nxt_path = path.c_str();
		path_cpy = wcsdup( path.c_str() );

		if( !path_cpy )
		{
			DIE_MEM();
		}

		for( nxt_path = wcstok( path_cpy, ARRAY_SEP_STR, &state );
			 nxt_path != 0;
			 nxt_path = wcstok( 0, ARRAY_SEP_STR, &state) )
		{
			wchar_t *expanded_path = expand_tilde_compat( wcsdup(nxt_path) );

//			debug( 2, L"woot %ls\n", expanded_path );

			int path_len = wcslen( expanded_path );
			if( path_len == 0 )
			{
				free(expanded_path );
				continue;
			}

			whole_path =
				wcsdupcat( expanded_path,
							( expanded_path[path_len-1] != L'/' )?L"/":L"",
							dir );

			free(expanded_path );

			struct stat buf;
			if( wstat( whole_path, &buf ) == 0 )
			{
				if( S_ISDIR(buf.st_mode) )
				{
					res = whole_path;
					break;
				}
				else
				{
					err = ENOTDIR;
				}
			}
			else
			{
				if( lwstat( whole_path, &buf ) == 0 )
				{
					err = EROTTEN;
				}
			}
			
			free( whole_path );
		}
		free( path_cpy );
	}

	if( !res )
	{
		errno = err;
	}

	return res;
}

bool path_can_get_cdpath(const wcstring &in) {
    wchar_t *tmp = path_allocate_cdpath(in.c_str());
    bool result = (tmp != NULL);
    free(tmp);
    return result;
}

wchar_t *path_get_config( void *context)
{
	int done = 0;
	wcstring res;
	
	const env_var_t xdg_dir  = env_get_string( L"XDG_CONFIG_HOME" );
	if( ! xdg_dir.missing() )
	{
		res = xdg_dir + L"/fish";
		if( !create_directory( res.c_str() ) )
		{
			done = 1;
		}
	}
	else
	{		
		const env_var_t home = env_get_string( L"HOME" );
		if( ! home.missing() )
		{
			res = home + L"/.config/fish";
			if( !create_directory( res.c_str() ) )
			{
				done = 1;
			}
		}
	}
	
	if( done )
	{
        wchar_t *result = wcsdup(res.c_str());
		halloc_register_function( context, &free, result );
		return result;
	}
	else
	{
		debug( 0, _(L"Unable to create a configuration directory for fish. Your personal settings will not be saved. Please set the $XDG_CONFIG_HOME variable to a directory where the current user has write access." ));
		return 0;
	}
	
}

wchar_t *path_make_canonical( void *context, const wchar_t *path )
{
	wchar_t *res = halloc_wcsdup( context, path );
	wchar_t *in, *out;
	
	in = out = res;
	
	while( *in )
	{
		if( *in == L'/' )
		{
			while( *(in+1) == L'/' )
			{
				in++;
			}
		}
		*out = *in;
	
		out++;
		in++;
	}

	while( 1 )
	{
		if( out == res )
			break;
		if( *(out-1) != L'/' )
			break;
		out--;
	}
	*out = 0;
		
	return res;
}