#include "sierrachart.h"
#include <CommCtrl.h>
#include <stdlib.h>
#include <tchar.h>

#pragma comment(lib, "comctl32.lib")

SCDLLName("TradeWindow")

typedef s_sc* SCPtr;

class Study {
protected:
    SCPtr _sc;
    virtual void Init() = 0;
    virtual void Clean() = 0;
    virtual void Run() = 0;

private:
    bool _initialised = false;

    void Start(SCStudyInterfaceRef sc_) {

        _sc = &sc_;

        if (!_initialised) {
            if (_sc->ChartWindowHandle == NULL)
                return;

            ::InitCommonControls();

            Init();
            _initialised = true;
        }
        /* inialisation of study params goes in here */
        if (_sc->SetDefaults) {
            _sc->GraphName = "Trade Window";
            _sc->StudyDescription = "Trade Window";
            _sc->AutoLoop = 1;
            _sc->GraphRegion = 0;
            _sc->SupportAttachedOrdersForTrading = false;
            _sc->AllowMultipleEntriesInSameDirection = true;
            _sc->AllowOppositeEntryWithOpposingPositionOrOrders = true;
            _sc->AllowEntryWithWorkingOrders = true;
            _sc->AllowOnlyOneTradePerBar = false;
            
            _sc->MaximumPositionAllowed = 3;
            
            return;
        }

        /* clean up when study removed */
        if (_sc->LastCallToFunction) {
            Clean();
            return;
        }

        Run();
    }
public:
    static void StartStudy(SCStudyInterfaceRef sc_);
};


class TradeWindow : public Study {

private:
    HWND _window;
    /* holder for orders created from window to be read when SC calls back into study */
    s_SCNewOrder buyOrder;
    s_SCNewOrder sellOrder;

    static const int ID_BTN_BUY = 100;
    static const int ID_BTN_SELL = 101;
    static const int ID_TXT_QTY = 103;
    static const int ID_CBO_OT = 104;
    
    static std::map<std::wstring, int> OrderTypeMap;

    /* Call back method from btn click. It doesn't seem to work calling the scstudyref directly from the window message pump so this method just sets up an order
    which will be picked up the next time the studys main loop is called. This could possible cause delay issues if the chart is slow to update for whatever reason */
    void ExecTrade(BuySellEnum side, int qty, int orderType) {
        s_SCNewOrder order;
        order.OrderQuantity = qty;
        order.TimeInForce = SCT_TIF_GOOD_TILL_CANCELED;
        order.OrderType = orderType;
        
        if (side == BSE_BUY)
            buyOrder = order;
        else
            sellOrder = order;
    }

    /* WNDPROC for WIN API Window handling */
    static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
		PAINTSTRUCT ps;
		HDC hdc;
        TradeWindow* study;
        RECT posType = { 50, 10, 100, 40 };
        RECT posQty = { 50, 50, 100, 80 };
        HWND cbo;

        /* Store a pointer back to the Trade window instance inside this static method */
        if (uMsg == WM_NCCREATE) {
            study = static_cast<TradeWindow*>(reinterpret_cast<CREATESTRUCT*>(lParam)->lpCreateParams);
            ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(study));
        } else {
            study = reinterpret_cast<TradeWindow*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
        }

        switch (uMsg) {
            /* Set up the common controls */
        case WM_CREATE:
            ::CreateWindowEx(WS_EX_WINDOWEDGE, WC_BUTTON, _T("B"), WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 50, 100, 80, 30, hWnd, (HMENU)ID_BTN_BUY, NULL, NULL);
            ::CreateWindowEx(WS_EX_WINDOWEDGE, WC_BUTTON, _T("S"), WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 150, 100, 80, 30, hWnd, (HMENU)ID_BTN_SELL, NULL, NULL);
            cbo = ::CreateWindowEx(WS_EX_WINDOWEDGE, WC_COMBOBOX, _T(""), CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_CHILD | WS_OVERLAPPED | WS_VISIBLE, 150, 10, 80, 150, hWnd, (HMENU)ID_CBO_OT, NULL, NULL);
            ::CreateWindowEx(WS_EX_WINDOWEDGE, WC_EDIT, _T("1"), WS_CHILD | WS_VISIBLE | ES_LEFT, 150, 50, 80, 30, hWnd, (HMENU)ID_TXT_QTY, NULL, NULL);

            for (auto& st : OrderTypeMap)
               ::SendMessage(cbo, CB_ADDSTRING, 0, (LPARAM)st.first.data());

            ::SendMessage(cbo, CB_SETCURSEL, 1, 0);
 
            break;
            /* handle mouse clicks */
        case WM_COMMAND:
            switch (HIWORD(wParam)) {
            case BN_CLICKED:
                LPTSTR txt = new TCHAR[20];
                ::GetWindowText(::GetDlgItem(hWnd, ID_TXT_QTY), txt, 20);
                int qty = _ttol(txt);
                
                ::GetWindowText(GetDlgItem(hWnd, ID_CBO_OT), txt, 20);
                int orderType = OrderTypeMap[std::wstring(txt)];

                delete[] txt;

                if (LOWORD(wParam) == ID_BTN_BUY) {
                    study->ExecTrade(BSE_BUY, qty, orderType);
                } else if(LOWORD(wParam) == ID_BTN_SELL) {
                    study->ExecTrade(BSE_SELL, qty, orderType);
                }
            }
            break;
            
        case WM_CLOSE:
			::DestroyWindow(hWnd);
			break;

        case WM_ERASEBKGND:
			return false;

            /* any custom painting goes in here */
		case WM_PAINT:
			hdc = BeginPaint(hWnd, &ps);

			::SetTextColor(hdc, RGB(0, 0, 0));
			::SetBkMode(hdc, TRANSPARENT);

            ::DrawText(hdc, _T("Type"), -1, &posType, DT_LEFT);
            ::DrawText(hdc, _T("Qty"), -1, &posQty, DT_LEFT);

			::EndPaint(hWnd, &ps);
			return 0;
			break;

        case WM_DESTROY:
            break;
		}
		return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }

protected:
    void Init() {
        HINSTANCE hInst = ::GetModuleHandle(NULL);

        WNDCLASSEX wcex;
        memset(&wcex, 0, sizeof(WNDCLASSEX));

        wcex.cbSize = sizeof(WNDCLASSEX);
        wcex.style = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc = WindowProc;
        wcex.hInstance = hInst;
        wcex.lpszClassName = _T("TradeWindow");

        ::RegisterClassEx(&wcex);
 
        _window = ::CreateWindowEx(WS_EX_WINDOWEDGE, _T("TradeWindow"), _T("Custom Trade Window"), WS_OVERLAPPEDWINDOW, 100, 100, 300, 200, NULL, NULL, hInst, this);

        if (!_window) {
            ::MessageBox(NULL, _T("Create window failed"), _T("Error"), MB_OK);
            return;
        }

        ::ShowWindow(_window, SW_SHOW);
        ::UpdateWindow(_window);
    }

    void Clean() {
        ::DestroyWindow(_window);
    }

    void Run() {
        /* any normal study processing can go in here if required */
        if (buyOrder.OrderQuantity > 0) {
            _sc->BuyEntry(buyOrder);
            buyOrder.OrderQuantity = 0;
        }
        else if (sellOrder.OrderQuantity > 0) {
            _sc->SellEntry(sellOrder);
            sellOrder.OrderQuantity = 0;
        }
    }

};

void Study::StartStudy(SCStudyInterfaceRef sc_) {
    
    /* bit of a fudge here as SC calls into the method when initialising the DLL from the add custom study screen 
    but we don't want to create the study at this point */
    const bool IsDLLInit = sc_.SetDefaults && sc_.ArraySize == 0;

    /* get/store the study pointer to retrieve on each call into the function */
    Study* study = IsDLLInit ? NULL : static_cast<Study*>(sc_.GetPersistentPointer(1));

    if (study == NULL) {
        study = new TradeWindow();
        sc_.SetPersistentPointer(1, study);
    }

    study->Start(sc_);

    if (sc_.LastCallToFunction || IsDLLInit) {
        delete study;
        sc_.SetPersistentPointer(1, NULL);
    }
}

std::map<std::wstring, int> TradeWindow::OrderTypeMap = {
    {L"Market", SCT_ORDERTYPE_MARKET},
    {L"Limit", SCT_ORDERTYPE_LIMIT},
    {L"Stop", SCT_ORDERTYPE_STOP}
};

/* SC Entry point into study */
SCSFExport scsf_TradeWindow(SCStudyInterfaceRef sc_) {
    Study::StartStudy(sc_);
}
