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

using Manina.Windows.Forms ;
using log4net ;

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

public partial class MainForm : Form
{
    private readonly HashSet<string> mValidImageExtensions = new HashSet<string>{ ".png", ".jpg", ".gif" } ;

    private Dictionary<string,ChartImage> mChartImages = new Dictionary<string,ChartImage>() ;
    private bool mIsReady = false ;

    private SplitContainer mSplitter = new SplitContainer() ;
    private UserControl mSearchUserControl = new UserControl() ;
    private Label mSearchLabel = new Label() ;
    private TextBox mSearchQuery = new TextBox() ;
    private AppImageListView mSearchResults = new AppImageListView() ;
    private WebBrowser mWebBrowser = new WebBrowser() ;
    private Panel mChartImagePanel = new Panel() ;
    private PictureBox mChartImagePictureBox = new PictureBox() ;

    private string mSearchResultsKey = null ;
    private Point? mMouseDragAnchor = null ;
    private Tuple<int,int> mScrollDragAnchor ;
    private double mCurrZoom, mDefaultZoom, mMinZoom, mMaxZoom ;
    private DateTime mLastKeyPressTimeStamp = DateTime.Now ;
    private bool mDisableProcessCmdKey = false ;
    private bool mSearchQueryTextChangedDisabled = false ;

    public MainForm()
    {
        // initialize the form
        InitializeComponent() ;
    }

    private void loadChartImages()
    {
        // initialize
        ILog logger = LogManager.GetLogger( "startup" ) ;

        // build an index of the config entries
        HashSet<string> configIndex = new HashSet<string>(
            Program.dataConfig.data.ToObject< Dictionary<string,object> >().Keys
        ) ;

        // locate the chart images
        string dataDir = Path.GetFullPath( Program.dataDir ) ;
        IEnumerable<string> files = Directory.EnumerateFiles(
            dataDir, "*.*", SearchOption.AllDirectories
        ) ;
        foreach( string fname in files ) {

            // load the next image
            string extn = Path.GetExtension( fname ).ToLower() ;
            if ( ! mValidImageExtensions.Contains( extn ) )
                continue ;
            string key ;
            string fullPath = Path.GetFullPath( fname ) ;
            if ( fullPath.StartsWith( dataDir ) ) {
                key = fname.Substring( dataDir.Length ) ;
                key = key.Replace( "\\", "/" ) ;
                if ( key.StartsWith( "/" ) )
                    key = key.Substring( 1 ) ;
            } else {
                // NOTE: I don't think we should ever get here :-/ If we do, the user will have to manage
                // their configuration using full paths for the image files, but at least we will still run...
                key = fullPath ;
            }
            logger.Debug( $"Loading image: {key}" ) ;
            dynamic config = Program.dataConfig.data[ key ] ;
            if ( config == null )
                Program.logStartupMsg( "unconfigured-image", fname.Substring(dataDir.Length+1) ) ;
            else
                configIndex.Remove( key ) ;
            ChartImage chartImage = new ChartImage( key, fullPath, config ) ;
            mChartImages[ key ] = chartImage ;

            // add the image to the list
            ImageListViewItem item = chartImage.imageListViewItem ;
            mSearchResults.Invoke( (MethodInvoker) ( () => mSearchResults.Items.Add( item ) ) ) ;
        }

        // log any config entries that were unused
        foreach( string key in configIndex ) {
            if ( key[0] == '_' )
                continue ;
            Program.logStartupMsg( "unused-config", key ) ;
        }

        // everything has been loaded, update the UI
        this.Invoke( (MethodInvoker) onStartupCompleted ) ;
    }

    private void onStartupCompleted()
    {
        // show any startup messages
        string unusedConfigs = Program.getStartupMsgs( "unused-config", "WARNING: Found unused configuration entries:" ) ;
        string unconfiguredImages = Program.getStartupMsgs( "unconfigured-image", "WARNING: Found image files with no configuration:" ) ;
        string badShortcuts = Program.getStartupMsgs( "bad-shortcut", "WARNING: Couldn't parse shortcuts:" ) ;
        string otherMsgs = Program.getStartupMsgs( "", "" ) ;
        if ( unusedConfigs != "" || unconfiguredImages != "" || badShortcuts != "" || otherMsgs != "" ) {
            string fname = Path.Combine( Program.resourcesDir, "startup-msgs.html" ) ;
            if ( File.Exists( fname ) ) {
                string buf = File.ReadAllText( fname ) ;
                buf = buf.Replace( "{{UNUSED-CONFIGS}}", unusedConfigs ) ;
                buf = buf.Replace( "{{UNCONFIGURED-IMAGES}}", unconfiguredImages ) ;
                buf = buf.Replace( "{{BAD-SHORTCUTS}}", badShortcuts ) ;
                buf = buf.Replace( "{{OTHER-MESSAGES}}", otherMsgs ) ;
                mWebBrowser.DocumentText = buf ;
            }
        } else {
            // NOTE: Nothing bad happened, just show the shortcuts report.
            showShortcuts() ;
        }

        // allow searches
        mSearchLabel.Enabled = true ;
        mSearchQuery.Enabled = true ;
        mSearchQuery.Focus() ;

        // flag that we've finished initialization
        mIsReady = true ;
    }

    private void updateSearchResults( string searchQuery )
    {
        // initialize
        searchQuery = searchQuery.Trim() ;
        ILog logger = LogManager.GetLogger( "search" ) ;
        logger.Info( $"Updating search results: query=\"{searchQuery}\"" ) ;

        // search for matching chart images
        List< Tuple<ChartImage,double> > results = new List<Tuple<ChartImage,double>>() ;
        foreach( ChartImage chartImage in mChartImages.Values ) {
            double score ;
            if ( searchQuery == "" )
                score = 0 ;
            else {
                score = chartImage.getSearchScore( searchQuery ) ;
                if ( score <= 0 )
                    continue ;
            }
            results.Add( new Tuple<ChartImage,double>( chartImage, score ) ) ;
        }

        // sort the search results
        results.Sort( (lhs, rhs) => {
            if ( lhs.Item2 < rhs.Item2 )
                return +1 ;
            else if ( lhs.Item2 > rhs.Item2 )
                return -1 ;
            // NOTE: We also sort by caption/filename to handle things like "foo-1" vs. "foo-2".
            return String.Compare( lhs.Item1.caption(), rhs.Item1.caption() ) ;
        } ) ;
        if ( searchQuery != "" && logger.IsInfoEnabled ) {
            if ( results.Count > 0 ) {
                logger.Info( "- Sorted results:" ) ;
                foreach ( var val in results )
                    logger.Info( $"  - \"{val.Item1.caption()}\" = {val.Item2:F1}" ) ;
            } else
                logger.Info( "- No results." ) ;
        } else
            logger.Info( "- No search query, showing all chart images." ) ;

        // show the search results
        List<ChartImage> results2 = new List<ChartImage>() ;
        foreach ( var r in results )
            results2.Add( r.Item1 ) ;
        loadSearchResults( results2, searchQuery, false ) ;
    }

    public void loadSearchResults( IEnumerable<ChartImage> chartImages, string key, bool clearSearchQuery )
    {
        // check if we need to reload the search results
        if ( key == mSearchResultsKey )
            return ;

        // initialize
        if ( chartImages == null ) {
            chartImages = mChartImages.Values ;
            key = "SHOW-ALL" ;
        }

        // clear the search results
        mSearchResults.SuspendLayout() ;
        mSearchResults.Items.Clear() ;
        mSearchResults.ResumeLayout( true ) ;

        // load the search results
        mSearchResults.SuspendLayout() ;
        int nItems = 0 ;
        foreach( ChartImage chartImage in chartImages ) {
            ImageListViewItem item = chartImage.imageListViewItem ;
            mSearchResults.Items.Add( item ) ;
            if ( nItems++ == 0 && key != "" && key != "SHOW-ALL" )
                item.Selected = true ;
        }
        mSearchResults.ResumeLayout( true ) ;
        mSearchResultsKey = key ;

        // clear the search query
        if ( clearSearchQuery )
            setSearchQuery( "", false ) ;

        // reset the repeated shortkey
        Shortcut.resetLastShortcut() ;
    }

    public void showShortcuts()
    {
        // load the report template
        string fname = Path.Combine( Program.resourcesDir, "shortcuts.html" ) ;
        if ( ! File.Exists( fname ) ) {
            Program.showErrorMsg( "Can't find the shortcuts report template." ) ;
            return ;
        }
        string htmlBuf = File.ReadAllText( fname ) ;

        // FUDGE! We also log the shortcuts, in case WebBrowser is not working e.g. on Mono :-/
        ILog logger = LogManager.GetLogger( "shortcuts" ) ;

        // generate HTML fragments for each type of shortcut
        Dictionary<Type,List<Shortcut>> registeredShortcuts = Shortcut.getRegisteredShortcuts() ;
        string makeShortcutsHtml( string caption, Type shortcutType ) {
            logger.Info( $"SHORTCUTS: {caption}" ) ;
            List<Shortcut> shortcuts ;
            if ( ! registeredShortcuts.TryGetValue( shortcutType, out shortcuts ) )
                return "" ;
            shortcuts.Sort( (lhs, rhs) => {
                int rc = String.Compare( lhs.getKeyCode().ToString().ToLower(), rhs.getKeyCode().ToString().ToLower() ) ;
                if ( rc != 0 )
                    return rc ;
                int nLhsModifiers = lhs.countModifiers() ;
                int nRhsModifiers = rhs.countModifiers() ;
                if ( nLhsModifiers < nRhsModifiers )
                    return -1 ;
                else if ( nLhsModifiers > nRhsModifiers )
                    return +1 ;
                else
                    return 0 ;
            } ) ;
            StringBuilder buf = new StringBuilder() ;
            buf.Append( $"<div class='header'> {caption} </div>" ) ;
            buf.Append( "<table class='shortcuts'>" ) ;
            foreach( Shortcut shortcut in shortcuts ) {
                logger.Info( $"- {shortcut} => {shortcut.textDescription()}" ) ;
                buf.Append( "<tr>" ) ;
                buf.Append( "<td class='key'>" + SecurityElement.Escape(shortcut.ToString()) ) ;
                buf.Append( "<td class='val'>" + shortcut.htmlDescription() ) ;
            }
            buf.Append( "</table>" ) ;
            return buf.ToString() ;
        }
        string htmlBuf2a = makeShortcutsHtml( "Search queries", typeof(SearchQueryShortcut) ) ;
        string htmlBuf2b = makeShortcutsHtml( "Images", typeof(ChartImageShortcut) ) ;

        // generate the shortcuts report
        if ( htmlBuf2a != "" && htmlBuf2b != "" ) {
            mWebBrowser.DocumentText = htmlBuf.Replace( "{{SHORTCUTS}}",
                "<table><tr><td style='padding-right:2em;'>" + htmlBuf2a + "<td>" + htmlBuf2b + "</table>"
            ) ;
        } else if ( htmlBuf2a != "" )
            mWebBrowser.DocumentText = htmlBuf.Replace( "{{SHORTCUTS}}", htmlBuf2a ) ;
        else if ( htmlBuf2b != "" )
            mWebBrowser.DocumentText = htmlBuf.Replace( "{{SHORTCUTS}}", htmlBuf2b ) ;
        else
            mWebBrowser.DocumentText = htmlBuf.Replace( "{{SHORTCUTS}}", "There are no shortcuts defined." ) ;
        mWebBrowser.BringToFront() ;
    }

    public void setSearchQuery( string queryString, bool raiseEvents=true )
    {
        // set the search query string
        if ( ! raiseEvents )
            mSearchQueryTextChangedDisabled = true ;
        mSearchQuery.Text = queryString ;
        if ( ! raiseEvents )
            mSearchQueryTextChangedDisabled = false ;
    }

    private void setChartImagePanelScrollPos( int? hscrollPos, int? vscrollPos )
    {
        // FUDGE! These need to be set twice to take effect on Windows :-/
        if ( hscrollPos != null ) {
            int scrollPos = Math.Min( Math.Max( hscrollPos.Value, 0 ), ChartImagePanel_MaxScrollX() ) ;
            mChartImagePanel.HorizontalScroll.Value = scrollPos ;
            mChartImagePanel.HorizontalScroll.Value = scrollPos ;
        }
        if ( vscrollPos != null ) {
            int scrollPos = Math.Min( Math.Max( vscrollPos.Value, 0 ), ChartImagePanel_MaxScrollY() ) ;
            mChartImagePanel.VerticalScroll.Value = scrollPos ;
            mChartImagePanel.VerticalScroll.Value = scrollPos ;
        }
    }

    private void scrollMainForm( int nLines, bool horz )
    {
        if ( horz ) {
            // scroll the ChartImage left/right
            HScrollProperties hscroll = mChartImagePanel.HorizontalScroll ;
            int scrollRange = hscroll.Maximum - hscroll.Minimum ;
            int scrollX = hscroll.Value + (int)( scrollRange * nLines/100 ) ;
            scrollChartImagePanel( scrollX, null ) ;
        } else {
            // scroll the ChartImage up/down
            VScrollProperties vscroll = mChartImagePanel.VerticalScroll ;
            int scrollRange = vscroll.Maximum - vscroll.Minimum ;
            int scrollY = vscroll.Value + (int)( scrollRange * nLines/100 ) ;
            scrollChartImagePanel( null, scrollY ) ;
        }
    }

    private void adjustZoom( double? delta, Point? clientMousePos )
    {
        // adjust the zoom
        double newZoom ;
        if ( delta != null )
            newZoom = Math.Max( Math.Min( mCurrZoom+delta.Value, mMaxZoom ), mMinZoom ) ;
        else
            newZoom = mDefaultZoom ;
        if ( Math.Abs( newZoom - mCurrZoom) > 0.001 ) {
            ILog logger = LogManager.GetLogger( "zoom" ) ;
            logger.Info( $"Setting image zoom: {newZoom:0.00}" ) ;
            mCurrZoom = newZoom ;
            doMainFormResize( clientMousePos ) ;
        }
    }

}
