/*
Copyright (C) 2016 Rory Walsh

Cabbage is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

Cabbage is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU General Public
License along with Csound; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA
*/

#pragma once


#include "../CabbageCommonHeaders.h"
#include "CabbageWidgetBase.h"
#include "../Audio/Plugins/CabbagePluginEditor.h"

/* A component that displays a piano keyboard, whose notes can be clicked on.*/
class MidiKeyboardDisplay : public Component,
	public MidiKeyboardStateListener,
	public ChangeBroadcaster
{
public:
	//==============================================================================
	/** The direction of the keyboard.
	@see setOrientation
	*/
	enum Orientation
	{
		horizontalKeyboard,
		verticalKeyboardFacingLeft,
		verticalKeyboardFacingRight,
	};

	/** Creates a MidiKeyboardDisplay.

	@param state        the midi keyboard model that this component will represent
	@param orientation  whether the keyboard is horizonal or vertical
	*/
	explicit MidiKeyboardDisplay(Orientation orientation);

	/** Destructor. */
	~MidiKeyboardDisplay() override;

	//==============================================================================
    void updateKeys();
    Array<int> notesDown;
	void setVelocity(float velocity, bool useMousePositionForVelocity);
	void setMidiChannel(int midiChannelNumber);
	int getMidiChannel() const noexcept { return midiChannel; }
    void setMidiChannelsToDisplay(int midiChannelMask);
    int getMidiChannelsToDisplay() const noexcept { return midiInChannelMask; }

	//==============================================================================
	/** Changes the width used to draw the white keys. */
	void setKeyWidth(float widthInPixels);

	/** Returns the width that was set by setKeyWidth(). */
	float getKeyWidth() const noexcept { return keyWidth; }

	/** Changes the width used to draw the buttons that scroll the keyboard up/down in octaves. */
	void setScrollButtonWidth(int widthInPixels);

	/** Returns the width that was set by setScrollButtonWidth(). */
	int getScrollButtonWidth() const noexcept { return scrollButtonWidth; }

	/** Changes the keyboard's current direction. */
	void setOrientation(Orientation newOrientation);

	/** Returns the keyboard's current direction. */
	Orientation getOrientation() const noexcept { return orientation; }

	/** Sets the range of midi notes that the keyboard will be limited to.

	By default the range is 0 to 127 (inclusive), but you can limit this if you
	only want a restricted set of the keys to be shown.

	Note that the values here are inclusive and must be between 0 and 127.
	*/
	void setAvailableRange(int lowestNote,
		int highestNote);

	/** Returns the first note in the available range.
	@see setAvailableRange
	*/
	int getRangeStart() const noexcept { return rangeStart; }

	/** Returns the last note in the available range.
	@see setAvailableRange
	*/
	int getRangeEnd() const noexcept { return rangeEnd; }

	/** If the keyboard extends beyond the size of the component, this will scroll
	it to show the given key at the start.

	Whenever the keyboard's position is changed, this will use the ChangeBroadcaster
	base class to send a callback to any ChangeListeners that have been registered.
	*/
	void setLowestVisibleKey(int noteNumber);

	/** Returns the number of the first key shown in the component.
	@see setLowestVisibleKey
	*/
	int getLowestVisibleKey() const noexcept { return (int)firstKey; }

	/** Sets the length of the black notes as a proportion of the white note length. */
	void setBlackNoteLengthProportion(float ratio) noexcept;

	/** Returns the length of the black notes as a proportion of the white note length. */
	float getBlackNoteLengthProportion() const noexcept { return blackNoteLengthRatio; }

	/** Returns the absolute length of the black notes.
	This will be their vertical or horizontal length, depending on the keyboard's orientation.
	*/
	float getBlackNoteLength() const noexcept;

	/** Sets the width of the black notes as a proportion of the white note width. */
	void setBlackNoteWidthProportion(float ratio) noexcept;

	/** Returns the width of the black notes as a proportion of the white note width. */
	float getBlackNoteWidthProportion() const noexcept { return blackNoteWidthRatio; }

	/** Returns the absolute width of the black notes.
	This will be their vertical or horizontal width, depending on the keyboard's orientation.
	*/
	float getBlackNoteWidth() const noexcept { return keyWidth * blackNoteWidthRatio; }

	/** If set to true, then scroll buttons will appear at either end of the keyboard
	if there are too many notes to fit them all in the component at once.
	*/
	void setScrollButtonsVisible(bool canScroll);

	//==============================================================================
	/** A set of colour IDs to use to change the colour of various aspects of the keyboard.

	These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
	methods.

	@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
	*/
	enum ColourIds
	{
		whiteNoteColourId = 0x1005000,
		blackNoteColourId = 0x1005001,
		keySeparatorLineColourId = 0x1005002,
		mouseOverKeyOverlayColourId = 0x1005003,  /**< This colour will be overlaid on the normal note colour. */
		keyDownOverlayColourId = 0x1005004,  /**< This colour will be overlaid on the normal note colour. */
		textLabelColourId = 0x1005005,
		upDownButtonBackgroundColourId = 0x1005006,
		upDownButtonArrowColourId = 0x1005007,
		shadowColourId = 0x1005008
	};

	/** Returns the position within the component of the left-hand edge of a key.

	Depending on the keyboard's orientation, this may be a horizontal or vertical
	distance, in either direction.
	*/
	float getKeyStartPosition(int midiNoteNumber) const;

	/** Returns the total width needed to fit all the keys in the available range. */
	float getTotalKeyboardWidth() const noexcept;

	/** Returns the key at a given coordinate. */
	int getNoteAtPosition(juce::Point<float> position);	
	void setOctaveForMiddleC(int octaveNumForMiddleC);

	/** This returns the value set by setOctaveForMiddleC().
	@see setOctaveForMiddleC
	*/
	int getOctaveForMiddleC() const noexcept { return octaveNumForMiddleC; }

	//==============================================================================
	/** @internal */
	void paint(Graphics&) override;
	/** @internal */
	void resized() override;
	/** @internal */
	void mouseMove(const MouseEvent&) override;
	/** @internal */
	void mouseDrag(const MouseEvent&) override;
	/** @internal */
	void mouseDown(const MouseEvent&) override;
	/** @internal */
	void mouseUp(const MouseEvent&) override;
	/** @internal */
	void mouseEnter(const MouseEvent&) override;
	/** @internal */
	void mouseExit(const MouseEvent&) override;
	/** @internal */
	void mouseWheelMove(const MouseEvent&, const MouseWheelDetails&) override;
	/** @internal */
	void focusLost(FocusChangeType) override;
	/** @internal */
	void handleNoteOn(MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override;
	/** @internal */
	void handleNoteOff(MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override;
	/** @internal */
	void colourChanged() override;

protected:
	//==============================================================================
	/** Draws a white note in the givenjuce::Rectangle.

	isOver indicates whether the mouse is over the key, isDown indicates whether the key is
	currently pressed down.

	When doing this, be sure to note the keyboard's orientation.
	*/
	virtual void drawWhiteNote(int midiNoteNumber,
		Graphics& g, juce::Rectangle<float> area,
		bool isDown, bool isOver,
		Colour lineColour, Colour textColour);

	/** Draws a black note in the givenjuce::Rectangle.

	isOver indicates whether the mouse is over the key, isDown indicates whether the key is
	currently pressed down.

	When doing this, be sure to note the keyboard's orientation.
	*/
	virtual void drawBlackNote(int midiNoteNumber,
		Graphics& g, juce::Rectangle<float> area,
		bool isDown, bool isOver,
		Colour noteFillColour);

	/** Allows text to be drawn on the white notes.
	By default this is used to label the C in each octave, but could be used for other things.
	@see setOctaveForMiddleC
	*/
	virtual String getWhiteNoteText(int midiNoteNumber);

	/** Draws the up and down buttons that scroll the keyboard up/down in octaves. */
	virtual void drawUpDownButton(Graphics& g, int w, int h,
		bool isMouseOver,
		bool isButtonPressed,
		bool movesOctavesUp);

	/** Callback when the mouse is clicked on a key.

	You could use this to do things like handle right-clicks on keys, etc.

	Return true if you want the click to trigger the note, or false if you
	want to handle it yourself and not have the note played.

	@see mouseDraggedToKey
	*/
	virtual bool mouseDownOnKey(int midiNoteNumber, const MouseEvent& e);

	/** Callback when the mouse is dragged from one key onto another.
	@see mouseDownOnKey
	*/
	virtual void mouseDraggedToKey(int midiNoteNumber, const MouseEvent& e);

	/** Callback when the mouse is released from a key.
	@see mouseDownOnKey
	*/
	virtual void mouseUpOnKey(int midiNoteNumber, const MouseEvent& e);

	/** Calculates the position of a given midi-note.

	This can be overridden to create layouts with custom key-widths.

	@param midiNoteNumber   the note to find
	@param keyWidth         the desired width in pixels of one key - see setKeyWidth()
	@returns                the start and length of the key along the axis of the keyboard
	*/
	virtual Range<float> getKeyPosition(int midiNoteNumber, float keyWidth) const;

	/** Returns thejuce::Rectangle for a given key if within the displayable range */
	juce::Rectangle<float> getRectangleForKey(int midiNoteNumber) const;


private:
	//==============================================================================
	struct UpDownButton;

	//MidiKeyboardState& state;
	float blackNoteLengthRatio = 0.7f;
	float blackNoteWidthRatio = 0.7f;
	float xOffset = 0;
	float keyWidth = 16.0f;
	int scrollButtonWidth = 12;
	Orientation orientation;

	int midiChannel = 1, midiInChannelMask = 0xffff;
	float velocity = 1.0f;

	Array<int> mouseOverNotes, mouseDownNotes;
	BigInteger keysPressed, keysCurrentlyDrawnDown;
	bool shouldCheckState = false;

	int rangeStart = 0, rangeEnd = 127;
	float firstKey = 12 * 4.0f;
	bool canScroll = true, useMousePositionForVelocity = true, shouldCheckMousePos = false;
	std::unique_ptr<Button> scrollDown, scrollUp;

	Array<KeyPress> keyPresses;
	Array<int> keyPressNotes;
	int keyMappingOctave = 6, octaveNumForMiddleC = 3;

	Range<float> getKeyPos(int midiNoteNumber) const;
	int xyToNote(juce::Point<float>, float& mousePositionVelocity);
	int remappedXYToNote(juce::Point<float>, float& mousePositionVelocity) const;
	void resetAnyKeysInUse();
	void updateNoteUnderMouse(juce::Point<float>, bool isDown, int fingerNum);
	void updateNoteUnderMouse(const MouseEvent&, bool isDown);
	void repaintNote(int midiNoteNumber);
	void setLowestVisibleKeyFloat(float noteNumber);

#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
	// Note that the parameters for these method have changed
	virtual int getKeyPosition(int, float, int&, int&) const { return 0; }
	virtual int drawWhiteNote(int, Graphics&, int, int, int, int, bool, bool, const Colour&, const Colour&) { return 0; }
	virtual int drawBlackNote(int, Graphics&, int, int, int, int, bool, bool, const Colour&) { return 0; }
#endif

	JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MidiKeyboardDisplay)
};


// Add any new custom widgets here to avoid having to edit makefiles and projects
// Each Cabbage widget should inherit from ValueTree listener, and CabbageWidgetBase
class CabbageKeyboardDisplay : public MidiKeyboardDisplay, public ValueTree::Listener, public CabbageWidgetBase
{
	int scrollbars;
	float keyWidth;
	String kind;
    CabbagePluginEditor* owner = {};

public:

	explicit CabbageKeyboardDisplay(ValueTree wData, CabbagePluginEditor* _owner);
	~CabbageKeyboardDisplay() override {
		widgetData.removeListener(this);
	}

	//VlaueTree::Listener virtual methods....
	void valueTreePropertyChanged(ValueTree& valueTree, const Identifier&) override;
	void valueTreeChildAdded(ValueTree&, ValueTree&)override {}
	void valueTreeChildRemoved(ValueTree&, ValueTree&, int) override {}
	void valueTreeChildOrderChanged(ValueTree&, int, int) override {}
	void valueTreeParentChanged(ValueTree&) override {}

	ValueTree widgetData;
    void colourPressedNotes(ValueTree wData);
	void updateColours(ValueTree& wData);
	JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CabbageKeyboardDisplay)
};
