/*
  ==============================================================================

   This file is part of the JUCE examples.
   Copyright (c) 2022 - Raw Material Software Limited

   The code included in this file is provided under the terms of the ISC license
   http://www.isc.org/downloads/software-support-policy/isc-license. Permission
   To use, copy, modify, and/or distribute this software for any purpose with or
   without fee is hereby granted provided that the above copyright notice and
   this permission notice appear in all copies.

   THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
   WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
   PURPOSE, ARE DISCLAIMED.

  ==============================================================================
*/

/*******************************************************************************
 The block below describes the properties of this PIP. A PIP is a short snippet
 of code that can be read by the Projucer and used to generate a JUCE project.

 BEGIN_JUCE_PIP_METADATA

 name:             MenusDemo
 version:          1.0.0
 vendor:           JUCE
 website:          http://juce.com
 description:      Showcases menu features.

 dependencies:     juce_core, juce_data_structures, juce_events, juce_graphics,
                   juce_gui_basics, juce_gui_extra
 exporters:        xcode_mac, vs2022, linux_make, androidstudio, xcode_iphone

 moduleFlags:      JUCE_STRICT_REFCOUNTEDPOINTER=1

 type:             Component
 mainClass:        MenusDemo

 useLocalCopy:     1

 END_JUCE_PIP_METADATA

*******************************************************************************/

#pragma once

#include "../Assets/DemoUtilities.h"

//==============================================================================
/**
    This struct contains a header component that will be used when the burger menu
    is enabled. It contains an icon that can be used to show the side panel containing
    the menu.
*/
struct BurgerMenuHeader  : public Component
{
    BurgerMenuHeader (SidePanel& sp)
        : sidePanel (sp)
    {
        static const unsigned char burgerMenuPathData[]
            = { 110,109,0,0,128,64,0,0,32,65,108,0,0,224,65,0,0,32,65,98,254,212,232,65,0,0,32,65,0,0,240,65,252,
                169,17,65,0,0,240,65,0,0,0,65,98,0,0,240,65,8,172,220,64,254,212,232,65,0,0,192,64,0,0,224,65,0,0,
                192,64,108,0,0,128,64,0,0,192,64,98,16,88,57,64,0,0,192,64,0,0,0,64,8,172,220,64,0,0,0,64,0,0,0,65,
                98,0,0,0,64,252,169,17,65,16,88,57,64,0,0,32,65,0,0,128,64,0,0,32,65,99,109,0,0,224,65,0,0,96,65,108,
                0,0,128,64,0,0,96,65,98,16,88,57,64,0,0,96,65,0,0,0,64,4,86,110,65,0,0,0,64,0,0,128,65,98,0,0,0,64,
                254,212,136,65,16,88,57,64,0,0,144,65,0,0,128,64,0,0,144,65,108,0,0,224,65,0,0,144,65,98,254,212,232,
                65,0,0,144,65,0,0,240,65,254,212,136,65,0,0,240,65,0,0,128,65,98,0,0,240,65,4,86,110,65,254,212,232,
                65,0,0,96,65,0,0,224,65,0,0,96,65,99,109,0,0,224,65,0,0,176,65,108,0,0,128,64,0,0,176,65,98,16,88,57,
                64,0,0,176,65,0,0,0,64,2,43,183,65,0,0,0,64,0,0,192,65,98,0,0,0,64,254,212,200,65,16,88,57,64,0,0,208,
                65,0,0,128,64,0,0,208,65,108,0,0,224,65,0,0,208,65,98,254,212,232,65,0,0,208,65,0,0,240,65,254,212,
                200,65,0,0,240,65,0,0,192,65,98,0,0,240,65,2,43,183,65,254,212,232,65,0,0,176,65,0,0,224,65,0,0,176,
                65,99,101,0,0 };

        Path p;
        p.loadPathFromData (burgerMenuPathData, sizeof (burgerMenuPathData));
        burgerButton.setShape (p, true, true, false);

        burgerButton.onClick = [this] { showOrHide(); };
        addAndMakeVisible (burgerButton);
    }

    ~BurgerMenuHeader() override
    {
        sidePanel.showOrHide (false);
    }

private:
    void paint (Graphics& g) override
    {
        auto titleBarBackgroundColour = getLookAndFeel().findColour (ResizableWindow::backgroundColourId)
                                                        .darker();

        g.setColour (titleBarBackgroundColour);
        g.fillRect (getLocalBounds());
    }

    void resized() override
    {
        auto r = getLocalBounds();

        burgerButton.setBounds (r.removeFromRight (40).withSizeKeepingCentre (20, 20));

        titleLabel.setFont (Font ((float) getHeight() * 0.5f, Font::plain));
        titleLabel.setBounds (r);
    }

    void showOrHide()
    {
        sidePanel.showOrHide (! sidePanel.isPanelShowing());
    }

    SidePanel& sidePanel;

    Label titleLabel         { "titleLabel", "JUCE Demo" };
    ShapeButton burgerButton { "burgerButton", Colours::lightgrey, Colours::lightgrey, Colours::white };

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BurgerMenuHeader)
};

//==============================================================================
class MenusDemo    : public Component,
                     public ApplicationCommandTarget,
                     public MenuBarModel
{
public:
    //==============================================================================
    /** A list of the commands that this demo responds to. */
    enum CommandIDs
    {
        menuPositionInsideWindow = 1,
        menuPositionGlobalMenuBar,
        menuPositionBurgerMenu,
        outerColourRed,
        outerColourGreen,
        outerColourBlue,
        innerColourRed,
        innerColourGreen,
        innerColourBlue
    };

    //==============================================================================
    /** Represents the possible menu positions. */
    enum class MenuBarPosition
    {
        window,
        global,
        burger
    };

    //==============================================================================
    MenusDemo()
    {
        menuBar.reset (new MenuBarComponent (this));
        addAndMakeVisible (menuBar.get());
        setApplicationCommandManagerToWatch (&commandManager);
        commandManager.registerAllCommandsForTarget (this);

        // this ensures that commands invoked on the DemoRunner application are correctly
        // forwarded to this demo
        commandManager.setFirstCommandTarget (this);

        // this lets the command manager use keypresses that arrive in our window to send out commands
        addKeyListener (commandManager.getKeyMappings());

        addChildComponent (menuHeader);
        addAndMakeVisible (outerCommandTarget);
        addAndMakeVisible (sidePanel);

        setWantsKeyboardFocus (true);

        setSize (500, 500);
    }

    ~MenusDemo() override
    {
       #if JUCE_MAC
        MenuBarModel::setMacMainMenu (nullptr);
       #endif

        commandManager.setFirstCommandTarget (nullptr);
    }

    void resized() override
    {
        auto b = getLocalBounds();

        if (menuBarPosition == MenuBarPosition::window)
        {
            menuBar->setBounds (b.removeFromTop (LookAndFeel::getDefaultLookAndFeel()
                                                             .getDefaultMenuBarHeight()));
        }
        else if (menuBarPosition == MenuBarPosition::burger)
        {
            menuHeader.setBounds (b.removeFromTop (40));
        }

        outerCommandTarget.setBounds (b);
    }

    //==============================================================================
    StringArray getMenuBarNames() override
    {
        return { "Menu Position", "Outer Colour", "Inner Colour" };
    }

    PopupMenu getMenuForIndex (int menuIndex, const String& /*menuName*/) override
    {
        PopupMenu menu;

        if (menuIndex == 0)
        {
            menu.addCommandItem (&commandManager, CommandIDs::menuPositionInsideWindow);
           #if JUCE_MAC
            menu.addCommandItem (&commandManager, CommandIDs::menuPositionGlobalMenuBar);
           #endif
            menu.addCommandItem (&commandManager, CommandIDs::menuPositionBurgerMenu);
        }
        else if (menuIndex == 1)
        {
            menu.addCommandItem (&commandManager, CommandIDs::outerColourRed);
            menu.addCommandItem (&commandManager, CommandIDs::outerColourGreen);
            menu.addCommandItem (&commandManager, CommandIDs::outerColourBlue);
        }
        else if (menuIndex == 2)
        {
            menu.addCommandItem (&commandManager, CommandIDs::innerColourRed);
            menu.addCommandItem (&commandManager, CommandIDs::innerColourGreen);
            menu.addCommandItem (&commandManager, CommandIDs::innerColourBlue);
        }

        return menu;
    }

    void menuItemSelected (int /*menuItemID*/, int /*topLevelMenuIndex*/) override {}

    //==============================================================================
    // The following methods implement the ApplicationCommandTarget interface, allowing
    // this window to publish a set of actions it can perform, and which can be mapped
    // onto menus, keypresses, etc.

    ApplicationCommandTarget* getNextCommandTarget() override
    {
        return &outerCommandTarget;
    }

    void getAllCommands (Array<CommandID>& c) override
    {
        Array<CommandID> commands { CommandIDs::menuPositionInsideWindow,
                                    CommandIDs::menuPositionGlobalMenuBar,
                                    CommandIDs::menuPositionBurgerMenu };
        c.addArray (commands);
    }

    void getCommandInfo (CommandID commandID, ApplicationCommandInfo& result) override
    {
        switch (commandID)
        {
            case CommandIDs::menuPositionInsideWindow:
                result.setInfo ("Inside Window", "Places the menu bar inside the application window", "Menu", 0);
                result.setTicked (menuBarPosition == MenuBarPosition::window);
                result.addDefaultKeypress ('w', ModifierKeys::shiftModifier);
                break;
            case CommandIDs::menuPositionGlobalMenuBar:
                result.setInfo ("Global", "Uses a global menu bar", "Menu", 0);
                result.setTicked (menuBarPosition == MenuBarPosition::global);
                result.addDefaultKeypress ('g', ModifierKeys::shiftModifier);
                break;
            case CommandIDs::menuPositionBurgerMenu:
                result.setInfo ("Burger Menu", "Uses a burger menu", "Menu", 0);
                result.setTicked (menuBarPosition == MenuBarPosition::burger);
                result.addDefaultKeypress ('b', ModifierKeys::shiftModifier);
                break;
            default:
                break;
        }
    }

    bool perform (const InvocationInfo& info) override
    {
        switch (info.commandID)
        {
            case CommandIDs::menuPositionInsideWindow:
                setMenuBarPosition (MenuBarPosition::window);
                break;
            case CommandIDs::menuPositionGlobalMenuBar:
                setMenuBarPosition (MenuBarPosition::global);
                break;
            case CommandIDs::menuPositionBurgerMenu:
                setMenuBarPosition (MenuBarPosition::burger);
                break;
            default:
                return false;
        }

        return true;
    }

    void setMenuBarPosition (MenuBarPosition newPosition)
    {
        if (menuBarPosition != newPosition)
        {
            menuBarPosition = newPosition;

            if (menuBarPosition != MenuBarPosition::burger)
                sidePanel.showOrHide (false);

           #if JUCE_MAC
            MenuBarModel::setMacMainMenu (menuBarPosition == MenuBarPosition::global ? this : nullptr);
           #endif

            menuBar->setVisible   (menuBarPosition == MenuBarPosition::window);
            burgerMenu.setModel   (menuBarPosition == MenuBarPosition::burger ? this : nullptr);
            menuHeader.setVisible (menuBarPosition == MenuBarPosition::burger);

            sidePanel.setContent  (menuBarPosition == MenuBarPosition::burger ? &burgerMenu : nullptr, false);
            menuItemsChanged();

            resized();
        }
    }

private:
   #if JUCE_DEMO_RUNNER
    ApplicationCommandManager& commandManager = getGlobalCommandManager();
   #else
    ApplicationCommandManager commandManager;
   #endif

    std::unique_ptr<MenuBarComponent> menuBar;
    MenuBarPosition menuBarPosition = MenuBarPosition::window;

    SidePanel sidePanel { "Menu", 300, false };

    BurgerMenuComponent burgerMenu;
    BurgerMenuHeader menuHeader { sidePanel };

    //==============================================================================
    /**
        Command messages that aren't handled in the main component will be passed
        to this class to respond to.
    */
    class OuterCommandTarget    : public Component,
                                  public ApplicationCommandTarget
    {
    public:
        OuterCommandTarget (ApplicationCommandManager& m)
            : commandManager (m),
              innerCommandTarget (commandManager)
        {
            commandManager.registerAllCommandsForTarget (this);

            addAndMakeVisible (innerCommandTarget);
        }

        void resized() override
        {
            innerCommandTarget.setBounds (getLocalBounds().reduced (50));
        }

        void paint (Graphics& g) override
        {
            g.fillAll (currentColour);
        }

        //==============================================================================
        ApplicationCommandTarget* getNextCommandTarget() override
        {
            return &innerCommandTarget;
        }

        void getAllCommands (Array<CommandID>& c) override
        {
            Array<CommandID> commands { CommandIDs::outerColourRed,
                                        CommandIDs::outerColourGreen,
                                        CommandIDs::outerColourBlue };

            c.addArray (commands);
        }

        void getCommandInfo (CommandID commandID, ApplicationCommandInfo& result) override
        {
            switch (commandID)
            {
                case CommandIDs::outerColourRed:
                    result.setInfo ("Red", "Sets the outer colour to red", "Outer", 0);
                    result.setTicked (currentColour == Colours::red);
                    result.addDefaultKeypress ('r', ModifierKeys::commandModifier);
                    break;
                case CommandIDs::outerColourGreen:
                    result.setInfo ("Green", "Sets the outer colour to green", "Outer", 0);
                    result.setTicked (currentColour == Colours::green);
                    result.addDefaultKeypress ('g', ModifierKeys::commandModifier);
                    break;
                case CommandIDs::outerColourBlue:
                    result.setInfo ("Blue", "Sets the outer colour to blue", "Outer", 0);
                    result.setTicked (currentColour == Colours::blue);
                    result.addDefaultKeypress ('b', ModifierKeys::commandModifier);
                    break;
                default:
                    break;
            }
        }

        bool perform (const InvocationInfo& info) override
        {
            switch (info.commandID)
            {
                case CommandIDs::outerColourRed:
                    currentColour = Colours::red;
                    break;
                case CommandIDs::outerColourGreen:
                    currentColour = Colours::green;
                    break;
                case CommandIDs::outerColourBlue:
                    currentColour = Colours::blue;
                    break;
                default:
                    return false;
            }

            repaint();
            return true;
        }

    private:
        //==============================================================================
        /**
            Command messages that aren't handled in the OuterCommandTarget will be passed
            to this class to respond to.
        */
        struct InnerCommandTarget    : public Component,
                                       public ApplicationCommandTarget
        {
            InnerCommandTarget (ApplicationCommandManager& m)
                : commandManager (m)
            {
                commandManager.registerAllCommandsForTarget (this);
            }

            void paint (Graphics& g) override
            {
                g.fillAll (currentColour);
            }

            //==============================================================================
            ApplicationCommandTarget* getNextCommandTarget() override
            {
                // this will return the next parent component that is an ApplicationCommandTarget
                return findFirstTargetParentComponent();
            }

            void getAllCommands (Array<CommandID>& c) override
            {
                Array<CommandID> commands { CommandIDs::innerColourRed,
                                            CommandIDs::innerColourGreen,
                                            CommandIDs::innerColourBlue };

                c.addArray (commands);
            }

            void getCommandInfo (CommandID commandID, ApplicationCommandInfo& result) override
            {
                switch (commandID)
                {
                    case CommandIDs::innerColourRed:
                        result.setInfo ("Red", "Sets the inner colour to red", "Inner", 0);
                        result.setTicked (currentColour == Colours::red);
                        result.addDefaultKeypress ('r', ModifierKeys::commandModifier | ModifierKeys::shiftModifier);
                        break;
                    case CommandIDs::innerColourGreen:
                        result.setInfo ("Green", "Sets the inner colour to green", "Inner", 0);
                        result.setTicked (currentColour == Colours::green);
                        result.addDefaultKeypress ('g', ModifierKeys::commandModifier | ModifierKeys::shiftModifier);
                        break;
                    case CommandIDs::innerColourBlue:
                        result.setInfo ("Blue", "Sets the inner colour to blue", "Inner", 0);
                        result.setTicked (currentColour == Colours::blue);
                        result.addDefaultKeypress ('b', ModifierKeys::commandModifier | ModifierKeys::shiftModifier);
                        break;
                    default:
                        break;
                }
            }

            bool perform (const InvocationInfo& info) override
            {
                switch (info.commandID)
                {
                    case CommandIDs::innerColourRed:
                        currentColour = Colours::red;
                        break;
                    case CommandIDs::innerColourGreen:
                        currentColour = Colours::green;
                        break;
                    case CommandIDs::innerColourBlue:
                        currentColour = Colours::blue;
                        break;
                    default:
                        return false;
                }

                repaint();
                return true;
            }

            ApplicationCommandManager& commandManager;

            Colour currentColour { Colours::blue };
        };

        ApplicationCommandManager& commandManager;
        InnerCommandTarget innerCommandTarget;

        Colour currentColour { Colours::red };
    };

    OuterCommandTarget outerCommandTarget { commandManager };

    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MenusDemo)
};
