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
{

    private string mFullPath ;
    private JsonConfig mJsonConfig ;
    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 ;
        mJsonConfig = new JsonConfig( config ) ;
        mImage = null ; // nb: we load this on demand

        // parse the shortcut
        ILog logger = LogManager.GetLogger( "shortcuts" ) ;
        string val = mJsonConfig.getStringVal( new string[]{"shortcut"} ) ;
        if ( val != "" ) {
            Keys? keys = Shortcut.parseShortcutString( val ) ;
            if ( keys == null )
                Program.logStartupMsg( "bad-shortcut", $"{key}: {val}" ) ;
            else {
                Shortcut shortcut = Shortcut.findRegisteredShortcut( keys.Value ) ;
                if ( shortcut == null ) {
                    shortcut = new ChartImageShortcut( keys.Value ) ;
                    Shortcut.registerShortcut( shortcut ) ;
                }
                if ( shortcut is ChartImageShortcut ) {
                    logger.Info( $"Registering image shortcut: {shortcut} => {caption()}" ) ;
                    ((ChartImageShortcut)shortcut).addChartImage( this ) ;
                }
                else
                    Program.logStartupMsg( "bad-shortcut", $"Found duplicate: {shortcut}" ) ;
            }
        }

        // prepare for search scoring
        if ( config != null && config["keywords"] != null ) {
            foreach( string kywd in config["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 double getSearchScore( string searchQuery )
    {
        // initialize
        searchQuery = searchQuery.ToUpper() ;
        List< Tuple<string,double> > scores = new List<Tuple<string,double>>() ;
        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
        double exactKeywordMatchScore = Program.appConfig.getDoubleVal( new string[]{"search","exactKeywordMatchScore"}, 10 ) ;
        double leadingPartialKeywordMatchScore = Program.appConfig.getDoubleVal( new string[]{"search","leadingPartialKeywordMatchScore"}, 2 ) ;
        double internalPartialKeywordMatchScore = Program.appConfig.getDoubleVal( new string[]{"search","internalPartialKeywordMatchScore"}, 1.5 ) ;
        double exactCaptionMatchScore = Program.appConfig.getDoubleVal( new string[]{"search","exactCaptionMatchScore"}, 5 ) ;
        double leadingPartialCaptionMatchScore = Program.appConfig.getDoubleVal( new string[]{"search","leadingPartialCaptionMatchScore"}, 1 ) ;
        double internalPartialCaptionMatchScore = Program.appConfig.getDoubleVal( new string[]{"search","internalPartialCaptionMatchScore"}, 0.5 ) ;

        // look for keyword matches
        foreach ( string kywd in mKeywords ) {
            if ( kywd == searchQuery ) {
                scores.Add( new Tuple<string,double>( "exactKeywordMatch", exactKeywordMatchScore ) ) ;
                continue ;
            }
            int pos = kywd.IndexOf( searchQuery ) ;
            if ( pos == 0 ) {
                scores.Add( new Tuple<string,double>(
                    $"leadingPartialKeywordMatch[{kywd}]",
                    Math.Min( leadingPartialKeywordMatchScore * searchQuery.Length, exactKeywordMatchScore )
                ) ) ;
            } else if ( pos > 0 ) {
                scores.Add( new Tuple<string,double>(
                    $"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,double>( "exactCaptionMatch", exactCaptionMatchScore ) ) ;
        else {
            int pos = caption.IndexOf( searchQuery ) ;
            if ( pos == 0 ) {
                scores.Add( new Tuple<string,double>(
                    $"leadingPartialCaptionMatch[{caption}]",
                    Math.Min( leadingPartialCaptionMatchScore * searchQuery.Length, exactCaptionMatchScore )
                ) ) ;
            } else if ( pos > 0 ) {
                scores.Add( new Tuple<string,double>(
                    $"internalPartialCaptionMatch[{caption}]",
                    Math.Min( internalPartialCaptionMatchScore * searchQuery.Length, exactCaptionMatchScore )
                ) ) ;
            }
        }

        // calculate the total score
        double 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 ;
    }

    public string caption()
    {
        // return the ChartImage's caption
        return jsonConfig.getStringVal(
            new string[]{ "caption" },
            Path.GetFileNameWithoutExtension( mFullPath )
        ) ;
    }

    public Image image()
    {
        // return the image data
        // NOTE: It would be nice to reuse the ImageListView cache, but it only seems to cache thumbnails.
        if ( mImage == null ) {
            try {
                mImage = Image.FromFile( mFullPath ) ;
            } catch( Exception ex ) {
                // NOTE: This crashes on Linux, but we can live with that - ChartImage's are created by iterating
                // over files in the data directory, so if we can't load the file, it'll be because the file was deleted
                // after startup, or a permissions problem i.e. things we don't really need to worry about.
                Program.showErrorMsg( $"Can't load image file:\n    {mFullPath}\n\n{ex}" ) ;
            }
        }
        return mImage ;
    }

    public JsonConfig jsonConfig { get { return mJsonConfig ; } }
    public ImageListViewItem imageListViewItem { get { return mImageListViewItem ; } }

}
