/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 1999-2001 Apple Computer, Inc.  All Rights Reserved. The
 * contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License.  Please
 * obtain a copy of the License at http://www.apple.com/publicsource and
 * read it before using this file.
 *
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.  Please
 * see the License for the specific language governing rights and
 * limitations under the License.
 *
 *
 * @APPLE_LICENSE_HEADER_END@
 *
 */





#include "PickerFromFile.h"


#include <stdlib.h>	
#include "GetWord.h"
#include "Trim.h"
#include "MyAssert.h"

#include <sys/stat.h>
#ifndef __Win32__
	#include <dirent.h>
#endif

#define kMaxPickerPath 512

#include <stdio.h>	

static bool CompareNameToElement( PLDoubleLinkedListNode<LoopDetectionListElement>* node, void* name );
static void DisplayPickerErr( int pickErr, const char *message, const char*fname, int lineCount, const char*lineBuff );

static bool CompareNameToElement( PLDoubleLinkedListNode<LoopDetectionListElement>* node, void* name )
{
	if ( !::strcmp( node->fElement->mPathName, (const char*)name ) )
		return true;
	
	return false;
}

static bool IsDir(char* path);
static bool IsDir(char* path)
{
	struct stat data;
	
	int err = stat(path, &data);
	
	if (err == -1)
		return false;
		
	return ((data.st_mode & S_IFDIR) == S_IFDIR );
}

static void DisplayPickerErr( int pickErr, const char *message, const char*fname, int lineCount, const char*lineBuff )
{
	char *errMessage;
	
	::printf( "- %s:\n", message );
	
	if ( lineCount )
		::printf( "  Playlist: %s, line# %i\n", fname, lineCount );
	else
		::printf( "  Playlist: %s\n", fname );
	
	if ( lineBuff )
		::printf( "  Playlist text: %s", lineBuff ); // lineBuff already includes a \n
	
	switch ( pickErr )
	{
		case kPickerPopulateLoopDetected:
			errMessage = "Include would create a loop.\n";
			break;

		case kPickerPopulateBadFormat:
			errMessage = "Playlist file is missing *PLAY-LIST* identifier.\n";
			break;
			
		case kPickerPopulateFileError:
			errMessage = "Playlist file could not be opened.\n";
			break;
			
		case kPickerPopulateNoMem:
		default:
			errMessage = "Internal error occurred.\n";
			break;
	}

	::printf( "  Reason: %s\n", errMessage);
}

bool PathIsAbsolute(char *pathPtr)
{
	bool result = false;
#ifdef __Win32__
	if ( (pathPtr[1] == ':') && ( pathPtr[2] == kPathDelimiterChar ) )
		result = true;
#else
	if ( *pathPtr == kPathDelimiterChar )
		result = true;
#endif
	return result;
}


int PopulatePickerFromFile( PlaylistPicker* picker, char* fname, const char* basePath, LoopDetectionList *ldList )
{
	Assert( picker );
	Assert( fname );
	
	FILE*						weightings = NULL;
	LoopDetectionListElement*	ldElement = NULL;
	LoopDetectionNode*			ldNode = NULL;
	int							lineCount = 0;
	int 						pickErr = kPickerPopulateNoErr;	
	char						path[kMaxPickerPath];
	
	
		
#if kPartialPathBeginsWithDelimiter
	if (PathIsAbsolute(fname))
	{
		if ( *basePath )
			fname++;
#else
	if ( !PathIsAbsolute(fname) )
	{
#endif
		// it's a partial path, expand it to include all
		// previously traversed paths
		::strncpy( path, basePath, kMaxPickerPath-1 );
		::strncat( path, fname, kMaxPickerPath-1 );
			
	}
	else
	{
		// it's an absolute reference. use the path
		// part of this for the new basePath
		::strncpy( path, fname, kMaxPickerPath-1 );
		
	}
	
	// path is now either an absolute or working directory
	// referenced partial path to the playlist file.
	int len = strlen(path);
	char lastChar = path[len-1];
	if (lastChar == '\n' || lastChar == '\r' || lastChar == ' ')
		path[len-1] = '\0';

	// ldList is passed as NULL by the initial caller.  recursive calls
	// pass along the ldList we create hre
	if ( ldList == NULL )
		ldList = new LoopDetectionList;

	Assert( ldList );
	
	if ( !ldList )
		pickErr = kPickerPopulateNoMem;
	
	
	if ( !pickErr )
	{
		if ( ldList->ForEachUntil( CompareNameToElement, path ) )
		{
			// we're already in the include chain, this is a loop
			// print a warning (error?) and continue past the loop.
			//printf("- Playlists include loop at file: %s\n", path );
			pickErr = kPickerPopulateLoopDetected;
		}
	}
	
	
	
	if ( !pickErr )
	{
		ldElement = new LoopDetectionListElement( path );
		
		Assert( ldElement );
		
		if ( ldElement )
		{	ldNode = new LoopDetectionNode( ldElement );
			Assert( ldNode );
			if ( !ldNode )
				pickErr = kPickerPopulateNoMem;
		}
		else
			pickErr = kPickerPopulateNoMem;
	}
	
	if (::IsDir(path))
		return ::PopulatePickerFromDir(picker, path);
	
	if ( !pickErr )
	{
		weightings = ::fopen( path, "r" );

		if (!weightings) 
		{
			printf("- Playlist picker failed opening list file %s\n", path);
			pickErr = kPickerPopulateFileError;
		}
	}
	
	if ( !pickErr )
	{
		long	lineBuffSize = (kMaxPickerPath *2) - 1;
		long	wordBuffSize = kMaxPickerPath - 1;
		
		char 	lineBuff[kMaxPickerPath * 2];
		char	wordBuff[kMaxPickerPath];
		char*	next;
		char*	pathEnd;
		char*	thisLine;
		
		// add ourselves to the list
		ldList->AddNode( ldNode );
		
		// trim off the file name to get just the path part
		pathEnd = ::strrchr( path, kPathDelimiterChar );
		
		if ( pathEnd )
		{	
			pathEnd++;
			*pathEnd = 0;
		}
		else
			*path = 0;
		
		thisLine = lineBuff;
		
		if ( ::fgets( lineBuff, lineBuffSize, weightings ) != NULL )
		{
			lineCount++;
			
			thisLine = ::TrimLeft( lineBuff );
			
			if ( 0 != ::strncmp(thisLine,"*PLAY-LIST*",11) )
			{	
				//::printf("- Playlist file missing *PLAY-LIST* identifier as first line:\n");
				//::printf("  %s%s\n", basePath, fname);
				pickErr = kPickerPopulateBadFormat;
			}
		}
		
			
		if ( !pickErr )
		{
			do 
			{	
				next = lineBuff;
				
				if ( ::fgets( lineBuff, lineBuffSize, weightings ) == NULL )
					break;
				
//				printf("line = %s\n", lineBuff);
				lineCount++;
				
				next = ::TrimLeft( lineBuff );
				
				if ( *next == '#' )
				{
					// it's a comment - just toss
					
					//if ( *next )
					//	printf( "comment: %s" , &lineBuff[1] );
					
				}
				else if (*next == '+') // a list
				{
					next = ::TrimLeft( next+1 );	// skip past + include
					
					if ( *next == '"' )	// get the name from the next part of the buff
						next = ::GetQuotedWord( wordBuff, next, wordBuffSize );
					else
						next = ::GetWord( wordBuff, next, wordBuffSize );
						
					
					
					// recusively populate from the include file.
					pickErr = PopulatePickerFromFile( picker, wordBuff, path, ldList );
					
					if ( pickErr )
					{	
						DisplayPickerErr( pickErr, "Playlist Include failed",  fname, lineCount, lineBuff );
						pickErr = kPickerPopulateNoErr;
					}
				}
				else if ( *next )
				{
					char	numBuff[32];
					char	expandedFileName[kMaxPickerPath];
					int 	weight = 10;	// default weight is 10

					// get the movie file name
					if ( *next == '"' )
						next = ::GetQuotedWord( wordBuff, next, wordBuffSize );
					else
						next = ::GetWord( wordBuff, next, wordBuffSize );
				
					if (*wordBuff)
					{
						#if kPartialPathBeginsWithDelimiter
						if ( PathIsAbsolute(wordBuff) )
						{
							char *wordStart = wordBuff;
							if ( *path )
								wordStart++;
							// full or partial path to the movie
							::strcpy( expandedFileName, path );
							::strcat( expandedFileName, wordStart );
						}
						#else
						if ( !PathIsAbsolute(wordBuff) )
						{
							// it's a partial path..
							
							// cat the path and fname to form the 
							// full or partial path to the movie
							::strcpy( expandedFileName, path );
							::strcat( expandedFileName, wordBuff );
						}
						#endif
						else
						{	// it's an absolute path..
							::strcpy( expandedFileName, wordBuff );
						}
						
						// then get the weighting ( if supplied )
						next = ::GetWord( numBuff, next, 32 );

						if ( *numBuff )
							weight = ::atoi(numBuff);

//						printf("expanded file name = %s\n", expandedFileName);
						if (::IsDir(expandedFileName))
							pickErr = ::PopulatePickerFromDir(picker, expandedFileName, weight);
						else if ( !picker->AddToList( expandedFileName, weight ) )
							pickErr = kPickerPopulateNoMem;
					}
				}
				
			} while ( feof( weightings ) == 0 && pickErr == kPickerPopulateNoErr );
		}
		
		// remove ourselves from the list
		ldList->RemoveNode( ldNode );
	}
	
	// only report unreported errors.
	if ( ldList && ldList->GetNumNodes() == 0 && pickErr )
		::DisplayPickerErr( pickErr, "Playlist error", fname, lineCount, NULL );
		
	
	if ( ldNode )
		delete ldNode; // node deletes element
	else if ( ldElement )
		delete ldElement;
	

	if ( weightings )
		(void)::fclose( weightings );

	if ( ldList && ldList->GetNumNodes() == 0 )
	{
		// all done now!
		delete ldList;
		ldList = NULL;
	
	}
	
	return pickErr;
}

int PopulatePickerFromDir( PlaylistPicker* picker, char* dirPath, int weight )
{
	static char expandedFileName[kMaxPickerPath];	// static so we don't build up the stack frame on recursion
	int pickErr = 0;
	if (dirPath != NULL)
		strcpy(expandedFileName, dirPath);
	
#ifdef __Win32__
	WIN32_FIND_DATA findData;
	HANDLE findResultHandle;
	Bool16 keepSearching = true;
	int len = strlen(expandedFileName);
	if (expandedFileName[len - 1] != kPathDelimiterChar)
	{
		expandedFileName[len] = kPathDelimiterChar;
		expandedFileName[len+1] = 0;
		len++;
	}
	strcat(expandedFileName, "*");
	
	findResultHandle = ::FindFirstFile( expandedFileName, &findData);
	if ( NULL == findResultHandle || INVALID_HANDLE_VALUE == findResultHandle )
	{
		//printf( "FindFirstFile( \"%s\" ): gle = %lu\n", searchPath, GetLastError() );
		return 0;
	}

    while ( (pickErr == 0) && keepSearching )
    {
		expandedFileName[len] = 0;	// retruncate name
		if (findData.cFileName[0] != '.')		// ignore anything beginning with a "."
		{
			strcat(expandedFileName, findData.cFileName);
			if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) 
				pickErr = PopulatePickerFromDir(picker, NULL, weight);
			else if ( !picker->AddToList( expandedFileName, weight ) )
				pickErr = kPickerPopulateNoMem;
		}

        keepSearching = FindNextFile( findResultHandle, &findData );
	}
		
#else
	DIR* dir;
	struct dirent* entry;
	int len = strlen(expandedFileName);
	
	if (expandedFileName[len - 1] != kPathDelimiterChar)
	{
		expandedFileName[len] = kPathDelimiterChar;
		expandedFileName[len+1] = 0;
		len++;
	}

	dir = opendir(expandedFileName);
	if (dir == NULL)
		return kPickerPopulateFileError;
		
	do {
		entry = readdir(dir);
		if (entry == NULL) break;
		
		if (entry->d_name[0] == '.')		// ignore anything beginning with a "."
			continue;

		if (len + strlen(entry->d_name) < kMaxPickerPath)
		{
			strcat(expandedFileName, entry->d_name);
			
#ifndef __solaris__
			if ((entry->d_type & DT_DIR) != 0)
#else
			if (::IsDir(expandedFileName))
#endif
				pickErr = PopulatePickerFromDir(picker, NULL, weight);
			else if ( !picker->AddToList( expandedFileName, weight ) )
				pickErr = kPickerPopulateNoMem;
		}
		expandedFileName[len] = 0;	// retruncate name
	} while (pickErr == 0);
	
	//close the directory back up
	(void)::closedir(dir);
	
#endif	
	return pickErr;
}



