using System ;
using System.Text ;
using System.IO ;
using System.Collections.Generic ;
using System.Windows.Forms ;
using System.Drawing ;

using Manina.Windows.Forms ;
using Newtonsoft.Json.Linq ;
using log4net ;

// --------------------------------------------------------------------

public class ChartImage
{
    // NOTE: This table maps shortcuts (e.g. Ctrl-Shift-C) to ChartImage's.
    private static Dictionary< Tuple<Keys,Keys>, List<ChartImage> > mShortcuts = new Dictionary< Tuple<Keys,Keys>, List<ChartImage> >() ;

    private string mFullPath ;
    private dynamic mConfig ;
    private HashSet<string> mKeywords = new HashSet<string>() ;
    private Image mImage ;
    private ImageListViewItem mImageListViewItem ;

    public ChartImage( string key, string fullPath, dynamic config )
    {
        // initialize the ChartImage
        mFullPath = fullPath ;
        mConfig = config ;
        if ( config == null )
            mConfig = new JObject() ;
        mImage = Image.FromFile( fullPath ) ;

        // parse the shortcut
        ILog logger = LogManager.GetLogger( "shortcuts" ) ;
        string val = mConfig[ "shortcut" ] ;
        if ( val != null ) {
            Tuple<Keys,Keys> shortcut = parseShortcut( val ) ;
            if ( shortcut == null )
                Program.logStartupMsg( "bad-shortcut", $"{key}: {val}" ) ;
            else {
                if ( ! mShortcuts.ContainsKey( shortcut ) )
                    mShortcuts[ shortcut ] = new List<ChartImage>() ;
                logger.Info( $"Registering shortcut: {shortcutString(shortcut.Item1,shortcut.Item2)} => ${caption()}" ) ;
                mShortcuts[ shortcut ].Add( this ) ;
            }
        }

        // prepare for search scoring
        if ( mConfig["keywords"] != null ) {
            foreach( string kywd in mConfig["keywords"] )
                mKeywords.Add( kywd.ToUpper() ) ;
        }

        // NOTE: Thumbnails are cached by ImageListViewItem GUID, so we reuse these objects,
        // instead of creating new ones every time we reload the search results.
        mImageListViewItem = new ImageListViewItem( fullPath ) ;
        mImageListViewItem.Tag = this ;
        mImageListViewItem.Text = caption() ;
    }

    public float getSearchScore( string searchQuery )
    {
        // initialize
        searchQuery = searchQuery.ToUpper() ;
        List< Tuple<string,float> > scores = new List<Tuple<string,float>>() ;
        ILog logger = LogManager.GetLogger( "search" ) ;
        if ( logger.IsDebugEnabled ) {
            if ( mKeywords.Count == 0 )
                logger.Debug( $"- \"{this.caption()}\": (no keywords)" ) ;
            else {
                StringBuilder buf2 = new StringBuilder() ;
                foreach( string kywd in mKeywords )
                    buf2.Append( $"{kywd} ; " ) ;
                string val = buf2.ToString() ;
                logger.Debug( $"- \"{this.caption()}\": [ {val.Substring(0,val.Length-3)} ]" ) ;
            }
        }

        // initialize the search score weights
        float exactKeywordMatchScore = Program.appConfig.getFloatVal( new string[]{"search","exactKeywordMatchScore"}, 10f ) ;
        float leadingPartialKeywordMatchScore = Program.appConfig.getFloatVal( new string[]{"search","leadingPartialKeywordMatchScore"}, 2f ) ;
        float internalPartialKeywordMatchScore = Program.appConfig.getFloatVal( new string[]{"search","internalPartialKeywordMatchScore"}, 1.5f ) ;
        float exactCaptionMatchScore = Program.appConfig.getFloatVal( new string[]{"search","exactCaptionMatchScore"}, 5f ) ;
        float leadingPartialCaptionMatchScore = Program.appConfig.getFloatVal( new string[]{"search","leadingPartialCaptionMatchScore"}, 1f ) ;
        float internalPartialCaptionMatchScore = Program.appConfig.getFloatVal( new string[]{"search","internalPartialCaptionMatchScore"}, 0.5f ) ;

        // look for keyword matches
        foreach ( string kywd in mKeywords ) {
            if ( kywd == searchQuery ) {
                scores.Add( new Tuple<string,float>( "exactKeywordMatch", exactKeywordMatchScore ) ) ;
                continue ;
            }
            int pos = kywd.IndexOf( searchQuery ) ;
            if ( pos == 0 ) {
                scores.Add( new Tuple<string,float>(
                    $"leadingPartialKeywordMatch[{kywd}]",
                    Math.Min( leadingPartialKeywordMatchScore * searchQuery.Length, exactKeywordMatchScore )
                ) ) ;
            } else if ( pos > 0 ) {
                scores.Add( new Tuple<string,float>(
                    $"internalPartialKeywordMatch[{kywd}]",
                    Math.Min( internalPartialKeywordMatchScore * searchQuery.Length, exactKeywordMatchScore )
                ) ) ;
            }
        }

        // look for caption matches
        string caption = this.caption().ToUpper() ;
        if ( searchQuery == caption )
            scores.Add( new Tuple<string,float>( "exactCaptionMatch", exactCaptionMatchScore ) ) ;
        else {
            int pos = caption.IndexOf( searchQuery ) ;
            if ( pos == 0 ) {
                scores.Add( new Tuple<string,float>(
                    $"leadingPartialCaptionMatch[{caption}]",
                    Math.Min( leadingPartialCaptionMatchScore * searchQuery.Length, exactCaptionMatchScore )
                ) ) ;
            } else if ( pos > 0 ) {
                scores.Add( new Tuple<string,float>(
                    $"internalPartialCaptionMatch[{caption}]",
                    Math.Min( internalPartialCaptionMatchScore * searchQuery.Length, exactCaptionMatchScore )
                ) ) ;
            }
        }

        // calculate the total score
        float totalScore = 0 ;
        StringBuilder buf = logger.IsDebugEnabled ? new StringBuilder() : null ;
        for ( int i=0 ; i < scores.Count ; ++i ) {
            totalScore += scores[i].Item2 ;
            if ( buf != null ) {
                if ( i > 0 )
                    buf.Append( " ; " ) ;
                buf.Append( $"{scores[i].Item1}={scores[i].Item2:F1}" ) ;
            }
        }
        if ( totalScore > 0 ) {
            logger.Debug( $"  - {buf}" ) ;
            logger.Debug( $"  - totalScore = {totalScore:F1}" ) ;
        }

        return totalScore ;
    }

    private Tuple<Keys,Keys> parseShortcut( string val )
    {
        // parse the shortcut
        string[] parts = val.ToLower().Split( "-" ) ;
        Keys modifiers = 0 ;
        for ( int i=0 ; i < parts.Length-1 ; ++i ) {
            if ( parts[i] == "ctrl" )
                modifiers |= Keys.Control ;
            else if ( parts[i] == "alt" )
                modifiers |= Keys.Alt ;
            else if ( parts[i] == "shift" )
                modifiers |= Keys.Shift ;
            else
                return null ;
        }
        Keys key ;
        bool rc = Enum.TryParse( parts[parts.Length-1], true, out key ) ;
        if ( ! rc )
            return null ;
        return new Tuple<Keys,Keys>( modifiers, key ) ;
    }

    public static List<ChartImage> checkShortcut( Keys modifiers, Keys key )
    {
        // check if there are any ChartImage's associated with the specified shortcut
        List<ChartImage> chartImages ;
        if ( ! mShortcuts.TryGetValue( new Tuple<Keys,Keys>( modifiers, key ), out chartImages ) )
            return null ;
        return chartImages ;
    }

    public static string shortcutString( Keys modifiers, Keys key )
    {
        // return the shortcut as a string
        StringBuilder buf = new StringBuilder() ;
        if ( (modifiers & Keys.Control) != 0 )
            buf.Append( "Ctrl-" ) ;
        if ( (modifiers & Keys.Shift) != 0 )
            buf.Append( "Shift-" ) ;
        if ( (modifiers & Keys.Alt) != 0 )
            buf.Append( "Alt-" ) ;
        buf.Append( key ) ;
        return buf.ToString() ;
    }

    public string caption()
    {
        string caption = (mConfig != null) ? mConfig["caption"] : null ;
        if ( caption == null )
            caption = Path.GetFileNameWithoutExtension( mFullPath ) ;
        return caption ;
    }

    public Image image { get { return mImage ; } }
    public ImageListViewItem imageListViewItem { get { return mImageListViewItem ; } }
}
