#include <windows.h>
#include <string>
#include <functional>
#define event(eventname, ... ) std::function<void(__VA_ARGS__ )> eventname

typedef COLORREF color;

using namespace std;

const char *labelpropname = "Cambalinho";
const char *labelclassprop = "classaddr";

enum MouseButtons
{
    None=-1,
    Left=0,
    Right=1,
    Middle=2,
    X1=3,
    X2=4
};

struct Position
{
	int X;
	int Y;
};

struct Size
{
	int Width;
	int Height;
};

class label
{
private:
    HWND hwnd;
    color clrTextColor=RGB(0,0,0);
    color clrBackColor=RGB(255,255,255);
    bool blnTransparent=false;
    string caption="label";

    HRGN CreateBitmapRgn(HWND image,color transparent=-1)
    {
        int    x=0, y=0;
        HRGN   hrgn;
        HRGN   hrgnTmp;
        RECT LabelSize;
        GetWindowRect(image,&LabelSize);
        int width=LabelSize.right-LabelSize.left;
        int height=LabelSize.bottom-LabelSize.top;
        hrgn = CreateRectRgn(0, 0, 0, 0);
        color pixelcolor=0;
        HDC hdcPixel=GetDC(image);
        for (y = 0; y < height; y++)
        {
            for (x = 0; x < width; x++)
            {
                pixelcolor=GetPixel(hdcPixel,x,y);
                if (pixelcolor!=transparent)
                {
                    hrgnTmp = CreateRectRgn(x, y, x + 1, y + 1);
                    CombineRgn(hrgn, hrgn, hrgnTmp, RGN_OR);
                    DeleteObject(hrgnTmp);
                }
            }
        }

        return hrgn;
    }

    void TrackMouse(HWND hwnd)
    {
        TRACKMOUSEEVENT tme;
        tme.cbSize = sizeof(TRACKMOUSEEVENT);
        tme.dwFlags = TME_HOVER | TME_LEAVE; //Type of events to track & trigger.
        tme.dwHoverTime = 5000; //How long the mouse has to be in the window to trigger a hover event.
        tme.hwndTrack = hwnd;
        TrackMouseEvent(&tme);
    }

    static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        WNDPROC oldproc = (WNDPROC)GetProp(GetParent(hwnd), labelpropname);
        if (oldproc == NULL)
            MessageBox(NULL, "oldprocnull", "oldprocnull", MB_OK);

        label *inst = (label*)GetProp(hwnd, labelclassprop);
        if (inst == NULL && msg == WM_CREATE)
         inst = (label*)(((LPCREATESTRUCT)lParam)->lpCreateParams);

        if ((inst == NULL) && (msg != WM_NCCREATE && msg != WM_NCCALCSIZE))
            MessageBox(NULL, "instnull", "instnull", MB_OK);
        static bool TrackingMouse = false;
        static bool WindowStopMoving = NULL;
        static bool WindowStopResize = NULL;
        static const UINT_PTR MouseStopTimerID = 1;
        HBRUSH g_hbrBackground = CreateSolidBrush(NULL_BRUSH);

        switch(msg)
        {
            case WM_CTLCOLORSTATIC:
            {
                HDC hdcStatic = (HDC)wParam;
                SetTextColor(hdcStatic, inst->clrTextColor);
                SetBkMode(hdcStatic, TRANSPARENT);
                SetBkColor(hdcStatic,inst->clrBackColor);
                g_hbrBackground = CreateSolidBrush(inst->clrBackColor);
                return (LONG)g_hbrBackground;
            }
            break;
            case WM_CREATE:
            {
                static int xPos = (int)(short) LOWORD(lParam);
                static int yPos = (int)(short) HIWORD(lParam);
                inst->Create(xPos,yPos);
            }
            break;

            case WM_MOVE:
            {
                static int xPos = (int)(short) LOWORD(lParam);
                static int yPos = (int)(short) HIWORD(lParam);
                inst->Move(xPos,yPos);
                WindowStopMoving=false;
                WindowStopResize=true;
            }
            break;
            case WM_SIZE :
            {
                WindowStopResize=false;
                WindowStopMoving=true;
            }
            break;
            case WM_EXITSIZEMOVE:
            {
                if(WindowStopResize==false)
                {
                    WindowStopResize=true;
                    inst->NotResize();
                }
                else if (WindowStopMoving==false)
                {
                    WindowStopMoving=true;
                    inst->Stop();
                }
            }
            break;

            case WM_NCHITTEST:
                return DefWindowProc(hwnd, msg, wParam, lParam);

            case WM_LBUTTONDOWN:
            case WM_RBUTTONDOWN:
            case WM_MBUTTONDOWN:
            case WM_XBUTTONDOWN:
            {
                SetFocus(inst->hwnd);
                static int xPos = (int)(short) LOWORD(lParam);
                static int yPos = (int)(short) HIWORD(lParam);
                bool blControl = ((wParam & MK_CONTROL) == MK_CONTROL);
                bool blshift = ((wParam & MK_SHIFT) == MK_SHIFT);
                MouseButtons MBButtons;
                if((wParam & MK_LBUTTON)!= false)
                    MBButtons=Left;
                else if (wParam & MK_RBUTTON)
                    MBButtons=Right;
                else if (wParam & MK_MBUTTON)
                    MBButtons=Middle;
                else if (wParam & MK_XBUTTON1)
                    MBButtons=X1;
                else if (wParam & MK_XBUTTON2)
                    MBButtons=X2;
                else
                    MBButtons=None;
                inst->MouseDown(MBButtons,blControl,blshift,xPos,yPos);
            }
            break;
            case WM_LBUTTONUP:
            case WM_RBUTTONUP:
            case WM_MBUTTONUP:
            case WM_XBUTTONUP:
            {
                SetFocus(inst->hwnd);
                static int xPos = (int)(short) LOWORD(lParam);
                static int yPos = (int)(short) HIWORD(lParam);
                bool blControl = ((wParam & MK_CONTROL) == MK_CONTROL);
                bool blshift = ((wParam & MK_SHIFT) == MK_SHIFT);
                MouseButtons MBButtons;
                if (msg == WM_LBUTTONUP)
                    MBButtons=Left;
                else if (msg == WM_RBUTTONUP)
                    MBButtons=Right;
                else if (msg == WM_MBUTTONUP)
                    MBButtons=Middle;
				else if (msg == WM_XBUTTONUP)
                {
                    if (wParam & MK_XBUTTON1)
                        MBButtons=X1;
                    else if (wParam & MK_XBUTTON2)
                        MBButtons=X2;
                }
                else
                    MBButtons=None;
                inst->MouseUp(MBButtons,blControl,blshift,xPos,yPos);
            }
            break;
            case WM_MOUSELEAVE :
            {
                TrackingMouse = false;
                inst->MouseLeave();
            }
            break;
            case WM_NCMOUSEMOVE:
            case WM_MOUSEMOVE:
            {
                if (!TrackingMouse)
                {
                    inst->TrackMouse(hwnd);
                    TrackingMouse = true;
                    inst->MouseEnter();
                }
                static int xPos = (int)(short) LOWORD(lParam);
                static int yPos = (int)(short) HIWORD(lParam);
                static bool blControl = ((wParam & MK_CONTROL) == MK_CONTROL);
                static bool blshift = ((wParam & MK_SHIFT) == MK_SHIFT);
                static MouseButtons MBButtons;
                if((wParam & MK_LBUTTON)!= false)
                    MBButtons=Left;
                else if ((wParam & MK_RBUTTON)!= false)
                    MBButtons=Right;
                else if ((wParam & MK_MBUTTON)!= false)
                    MBButtons=Middle;
                else if ((wParam & MK_XBUTTON1)!= false)
                    MBButtons=X1;
                else if ((wParam & MK_XBUTTON2)!= false)
                    MBButtons=X2;
                else
                    MBButtons=None;

                inst->MouseMove(MBButtons,blControl,blshift,xPos,yPos);
                KillTimer(hwnd, MouseStopTimerID);
                SetTimer(hwnd, MouseStopTimerID, 500, NULL);
            }
			break;
            case WM_MOUSEHOVER:
            {
                inst->MouseHover();
            }
            break;
            case WM_TIMER:
            switch(wParam)
            {
                case MouseStopTimerID:
                {
                    KillTimer(hwnd, MouseStopTimerID);
                    inst->MouseStoped();
                }
            }
            break;
            case WM_KEYUP:
                inst->KeyUp(lParam,wParam);
			break;
            case WM_KEYDOWN:
                inst->KeyDown(lParam,wParam);
			break;

            default:
			break;
        }

        return oldproc ? CallWindowProc(oldproc, hwnd, msg, wParam, lParam) : DefWindowProc(hwnd, msg, wParam, lParam);
    }


public:

    //is these 2 lines ok?
    //see the #define on top
    event(NotResize)=[](){;};
    event(Stop)=[](){;};
    event(MouseEnter)=[](){;};
    event(MouseLeave)=[](){;};
    event(Create,(int x, int y))=[](int x, int y){;};
    event(Move,(int x, int y))=[](int x, int y){;};
    event(MouseDown,(MouseButtons Button,bool control, bool shift, int x, int y))=[](int Button, bool alt, bool shift,int x, int y){;};
    event(MouseUp,(MouseButtons Button,bool control, bool shift, int x, int y))=[](int Button, bool alt, bool shift,int x, int y){;};
    event(MouseMove,(MouseButtons Button,bool control, bool shift, int x, int y))=[](int Button, bool alt, bool shift,int x, int y){;};
    event(MouseStoped)=[](){;};
    event(MouseHover)=[](){;};
    event(MouseClick,(MouseButtons Button,bool control, bool shift, int x, int y))=[](int Button, bool alt, bool shift,int x, int y){;};
    event(MouseDoubleClick,(MouseButtons Button,bool control, bool shift, int x, int y))=[](int Button, bool alt, bool shift,int x, int y){;};
    event(MouseWhell,(int Whell, MouseButtons Button,bool control, bool shift, int x, int y))=[](int Whell, int Button, bool alt, bool shift,int x, int y){;};
    event(KeyDown,(int repeat,int Key))=[](int repeat,int Key){;};
    event(KeyUp,(int repeat,int Key))=[](int repeat,int Key){;};

	~label()
	{
		DestroyWindow(hwnd);
		hwnd = 0;
	}
    label()
    {
        ;
    }

    void setparent(HWND parent)
    {
        static int i = 0;
        i++;
        string strclass=labelclassprop + to_string(i);
        labelclassprop=strclass.c_str();
        WNDCLASS wc;
		HINSTANCE mod = (HINSTANCE)GetModuleHandle(NULL);

	    ZeroMemory(&wc, sizeof(WNDCLASS));
		GetClassInfo(mod, "STATIC", &wc);

		wc.hInstance = mod;
		wc.lpszClassName = labelclassprop;
		wc.hbrBackground =  CreateSolidBrush(RGB(255,0,0));

		// store the old WNDPROC of the EDIT window class
        SetProp(parent, labelpropname, (HANDLE)wc.lpfnWndProc);

	// replace it with local WNDPROC
	wc.lpfnWndProc = WndProc;

	// register the new window class, "ShEdit"
	if (!RegisterClass(&wc))
		MessageBox(NULL, "error in register", "error", MB_OK);

        hwnd = CreateWindowEx(
            WS_EX_LEFT| WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR | WS_EX_TRANSPARENT,
            labelclassprop,
            labelclassprop,
            SS_LEFT|WS_CHILD|WS_VISIBLE|WS_OVERLAPPED,
            100, 100, 100, 100,
            parent,
            NULL,
            mod,
            (LPVOID)this);

	if (hwnd == NULL)
		MessageBox(NULL, "error in create", "error", MB_OK);

        SetProp(hwnd, labelclassprop, (HANDLE)this);
        //SetWindowLongPtr(this->hwnd,GWLP_USERDATA,(LONG) this);
        UpdateWindow(hwnd);
        clrBackColor=GetPixel(GetDC(hwnd),0,0);
    }

    Size GetSize()
    {
        RECT LabelSize;
        GetWindowRect(this->hwnd,&LabelSize);
        Size crdSize = {LabelSize.right-LabelSize.left,LabelSize.bottom-LabelSize.top};
        return crdSize;
    }

    void SetSize(int Width, int Height)
    {
        RECT LabelSize;
        GetWindowRect(this->hwnd,&LabelSize);
        LabelSize.right=LabelSize.left+Width;
        LabelSize.bottom=LabelSize.top+Height;
        SetWindowPos(this->hwnd,HWND_TOP,LabelSize.left,LabelSize.top,Width,Height,SWP_NOMOVE|SWP_NOZORDER);
    }

    Position GetPosition()
    {
        RECT LabelSize;
        GetWindowRect(this->hwnd,&LabelSize);
        Position crdSize = {LabelSize.left,LabelSize.top};
        return crdSize;
    }

    void SetPosition(int x, int y)
    {
        RECT LabelSize;
        GetWindowRect(this->hwnd,&LabelSize);
        SetWindowPos(this->hwnd,HWND_TOP,x,y,0,0,SWP_NOSIZE|SWP_NOZORDER);
    }



    void Transparent()
    {
        RECT LabelSize;
        GetWindowRect(this->hwnd,&LabelSize);
        HRGN hRgn = CreateBitmapRgn(this->hwnd, this->clrBackColor);
        SetWindowRgn(this->hwnd, hRgn, true);
    }

    void SetText(string text)
    {
        this->caption=text;
        SetWindowText(this->hwnd,text.c_str());
    }

    string GetText()
    {
        return this->caption;
    }

    void SetBackColor(COLORREF color)
    {
        clrBackColor=color;
        ShowWindow(this->hwnd,SW_HIDE);
        ShowWindow(this->hwnd,SW_SHOW);
    }

    color GetBackColor()
    {
        return clrBackColor;
    }

    void SetColorText(COLORREF color)
    {
        clrTextColor=color;
        ShowWindow(this->hwnd,SW_HIDE);
        ShowWindow(this->hwnd,SW_SHOW);
    }

    color GetColorText()
    {
        return this->clrTextColor;
    }

    HFONT GetFont()
    {
        HFONT actual;
        actual=(HFONT)SendMessage(hwnd,WM_GETFONT,0,0);
        return actual;
    }
    void SetFont(HFONT newfont)
    {
        SendMessage(hwnd,WM_SETFONT,(WPARAM)newfont,0);
    }
    void SetImage(string FileName)
    {
        HBITMAP bmpSource = NULL;
        bmpSource = (HBITMAP)LoadImage(NULL,FileName.c_str(), IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
        SendMessage(this->hwnd,STM_SETICON,(WPARAM) IMAGE_ICON,(LPARAM) bmpSource);
    }

    HWND GetHWND()
    {
        return hwnd;
    }
};
