using System ;
using System.Globalization ;
using System.IO ;
namespace Emby.Naming.TV
{
/// <summary>
/// Class to parse season paths.
/// </summary>
public static class SeasonPathParser
{
/// <summary>
/// A season folder must contain one of these somewhere in the name.
/// </summary>
private static readonly string [ ] _seasonFolderNames =
{
"season" ,
"sæson" ,
"temporada" ,
"saison" ,
"staffel" ,
"series" ,
"сезон" ,
"stagione"
} ;
/// <summary>
/// Attempts to parse season number from path.
/// </summary>
/// <param name="path">Path to season.</param>
/// <param name="supportSpecialAliases">Support special aliases when parsing.</param>
/// <param name="supportNumericSeasonFolders">Support numeric season folders when parsing.</param>
/// <returns>Returns <see cref="SeasonPathParserResult"/> object.</returns>
public static SeasonPathParserResult Parse ( string path , bool supportSpecialAliases , bool supportNumericSeasonFolders )
{
var result = new SeasonPathParserResult ( ) ;
var ( seasonNumber , isSeasonFolder ) = GetSeasonNumberFromPath ( path , supportSpecialAliases , supportNumericSeasonFolders ) ;
result . SeasonNumber = seasonNumber ;
if ( result . SeasonNumber . HasValue )
{
result . Success = true ;
result . IsSeasonFolder = isSeasonFolder ;
}
return result ;
}
/// <summary>
/// Gets the season number from path.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="supportSpecialAliases">if set to <c>true</c> [support special aliases].</param>
/// <param name="supportNumericSeasonFolders">if set to <c>true</c> [support numeric season folders].</param>
/// <returns>System.Nullable{System.Int32}.</returns>
private static ( int? SeasonNumber , bool IsSeasonFolder ) GetSeasonNumberFromPath (
string path ,
bool supportSpecialAliases ,
bool supportNumericSeasonFolders )
{
string filename = Path . GetFileName ( path ) ;
if ( supportSpecialAliases )
{
if ( string . Equals ( filename , "specials" , StringComparison . OrdinalIgnoreCase ) )
{
return ( 0 , true ) ;
}
if ( string . Equals ( filename , "extras" , StringComparison . OrdinalIgnoreCase ) )
{
return ( 0 , true ) ;
}
}
if ( supportNumericSeasonFolders )
{
if ( int . TryParse ( filename , NumberStyles . Integer , CultureInfo . InvariantCulture , out var val ) )
{
return ( val , true ) ;
}
}
if ( filename . StartsWith ( "s" , StringComparison . OrdinalIgnoreCase ) )
{
var testFilename = filename . AsSpan ( ) . Slice ( 1 ) ;
if ( int . TryParse ( testFilename , NumberStyles . Integer , CultureInfo . InvariantCulture , out var val ) )
{
return ( val , true ) ;
}
}
// Look for one of the season folder names
foreach ( var name in _seasonFolderNames )
{
if ( filename . Contains ( name , StringComparison . OrdinalIgnoreCase ) )
{
var result = GetSeasonNumberFromPathSubstring ( filename . Replace ( name , " " , StringComparison . OrdinalIgnoreCase ) ) ;
if ( result . SeasonNumber . HasValue )
{
return result ;
}
break ;
}
}
var parts = filename . Split ( new [ ] { '.' , '_' , ' ' , '-' } , StringSplitOptions . RemoveEmptyEntries ) ;
foreach ( var part in parts )
{
if ( TryGetSeasonNumberFromPart ( part , out int seasonNumber ) )
{
return ( seasonNumber , true ) ;
}
}
return ( null , true ) ;
}
private static bool TryGetSeasonNumberFromPart ( ReadOnlySpan < char > part , out int seasonNumber )
{
seasonNumber = 0 ;
if ( part . Length < 2 | | ! part . StartsWith ( "s" , StringComparison . OrdinalIgnoreCase ) )
{
return false ;
}
if ( int . TryParse ( part . Slice ( 1 ) , NumberStyles . Integer , CultureInfo . InvariantCulture , out var value ) )
{
seasonNumber = value ;
return true ;
}
return false ;
}
/// <summary>
/// Extracts the season number from the second half of the Season folder name (everything after "Season", or "Staffel").
/// </summary>
/// <param name="path">The path.</param>
/// <returns>System.Nullable{System.Int32}.</returns>
private static ( int? SeasonNumber , bool IsSeasonFolder ) GetSeasonNumberFromPathSubstring ( ReadOnlySpan < char > path )
{
var numericStart = - 1 ;
var length = 0 ;
var hasOpenParenthesis = false ;
var isSeasonFolder = true ;
// Find out where the numbers start, and then keep going until they end
for ( var i = 0 ; i < path . Length ; i + + )
{
if ( char . IsNumber ( path [ i ] ) )
{
if ( ! hasOpenParenthesis )
{
if ( numericStart = = - 1 )
{
numericStart = i ;
}
length + + ;
}
}
else if ( numericStart ! = - 1 )
{
// There's other stuff after the season number, e.g. episode number
isSeasonFolder = false ;
break ;
}
var currentChar = path [ i ] ;
if ( currentChar = = '(' )
{
hasOpenParenthesis = true ;
}
else if ( currentChar = = ')' )
{
hasOpenParenthesis = false ;
}
}
if ( numericStart = = - 1 )
{
return ( null , isSeasonFolder ) ;
}
return ( int . Parse ( path . Slice ( numericStart , length ) , provider : CultureInfo . InvariantCulture ) , isSeasonFolder ) ;
}
}
}