/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


#include "stdafx.h"
#include "utils.h"
#include "FindJava2Dlg.h"
#include "afxdialogex.h"
#include <atlpath.h>                            // ATL CPath

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#define COL_PATH 1


CFindJava2Dlg::CFindJava2Dlg(CWnd* pParent /*=NULL*/)
    : CDialog(CFindJava2Dlg::IDD, pParent), mSelectedIndex(-1) {
    m_hIcon = AfxGetApp()->LoadIcon(IDI_ANDROID_ICON);
}

void CFindJava2Dlg::DoDataExchange(CDataExchange* pDX) {
    CDialog::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_PATH_LIST, mPathsListCtrl);
    DDX_Control(pDX, IDOK, mOkButton);
}

BEGIN_MESSAGE_MAP(CFindJava2Dlg, CDialog)
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDC_BUTTON_ADD, &CFindJava2Dlg::OnBnClickedButtonAdd)
    ON_NOTIFY(NM_CLICK, IDC_PATH_LIST, &CFindJava2Dlg::OnNMClickPathList)
    ON_NOTIFY(LVN_ITEMCHANGED, IDC_PATH_LIST, &CFindJava2Dlg::OnLvnItemchangedPathList)
END_MESSAGE_MAP()


// -----
// CFindJava2Dlg message handlers

BOOL CFindJava2Dlg::OnInitDialog() {
    CDialog::OnInitDialog();

    SetWindowText(getAppName());

    // Set the icon for this dialog.  The framework does this automatically
    //  when the application's main window is not a dialog
    SetIcon(m_hIcon, TRUE);			// Set big icon
    SetIcon(m_hIcon, FALSE);		// Set small icon

    // Initialize list controls
    mPathsListCtrl.SetExtendedStyle(
        mPathsListCtrl.GetExtendedStyle() |
        LVS_EX_CHECKBOXES | 
        LVS_EX_FULLROWSELECT | 
        LVS_EX_GRIDLINES);

    // We want 2 columns: Java version and path
    mPathsListCtrl.InsertColumn(0, _T("Version"), LVCFMT_RIGHT, 60,  0);
    mPathsListCtrl.InsertColumn(1, _T("Path"),     LVCFMT_LEFT, 386, 0);

    mJavaFinder->findJavaPaths(&mPaths);
    fillPathsList();
    adjustButtons();

    return TRUE;  // return TRUE  unless you set the focus to a control
}

// If you add a minimize button to your dialog, you will need the code below
// to draw the icon.  For MFC applications using the document/view model,
// this is automatically done for you by the framework.
// [Note: MFC boilerplate, keep as-is]
void CFindJava2Dlg::OnPaint() {
    if (IsIconic()) {
        CPaintDC dc(this); // device context for painting

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // Center icon in client rectangle
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // Draw the icon
        dc.DrawIcon(x, y, m_hIcon);
    } else {
        CDialog::OnPaint();
    }
}

// The system calls this function to obtain the cursor to display while the user drags
// the minimized window. [Note: MFC boilerplate, keep as-is]
HCURSOR CFindJava2Dlg::OnQueryDragIcon() {
    return static_cast<HCURSOR>(m_hIcon);
}

// Button add has been pressed; use file dialog and add path if it's a valid java.exe
void CFindJava2Dlg::OnBnClickedButtonAdd() {
    CFileDialog fileDlg(
        TRUE,           // true=open dialog,  false=save-as dialog
        _T("exe"),      // lpszDefExt 
        _T("java.exe"), // lpszFileName 
        OFN_FILEMUSTEXIST || OFN_PATHMUSTEXIST,
        NULL,           // lpszFilter
        this);          // pParentWnd

    if (fileDlg.DoModal() == IDOK) {
        CString path = fileDlg.GetPathName();

        CJavaPath javaPath;
        if (!mJavaFinder->checkJavaPath(path, &javaPath)) {
            CString msg;
            if (javaPath.mVersion > 0) {
                msg.Format(_T("Insufficient Java Version found: expected %s, got %s"), 
                           CJavaPath(mJavaFinder->getMinVersion(), CPath()).getVersion(),
                           javaPath.getVersion());
            } else {
                msg.Format(_T("No valid Java Version found for %s"), path);
            }
            AfxMessageBox(msg, MB_OK);

        } else {
            if (mPaths.find(javaPath) == mPaths.end()) {
                // Path isn't known yet so add it and refresh the list.
                mPaths.insert(javaPath);
                fillPathsList();
            }

            // Select item in list and set mSelectedIndex
            selectPath(-1 /*index*/, &javaPath);
        }
    }
}

// An item in the list has been selected, select checkmark and set mSelectedIndex.
void CFindJava2Dlg::OnNMClickPathList(NMHDR *pNMHDR, LRESULT *pResult) {
    LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
    int index = pNMItemActivate->iItem;
    selectPath(index, nullptr);
    *pResult = TRUE;
}

// An item in the list has changed, toggle checkmark as needed.
void CFindJava2Dlg::OnLvnItemchangedPathList(NMHDR *pNMHDR, LRESULT *pResult) {
    *pResult = FALSE;
    LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);

    if ((pNMLV->uChanged & LVIF_STATE) != 0) {
        // Item's state has changed. Check the selection to see if it needs to be adjusted.
        int index = pNMLV->iItem;

        UINT oldState = pNMLV->uOldState;
        UINT newState = pNMLV->uNewState;

        if ((oldState & LVIS_STATEIMAGEMASK) != 0 || (newState & LVIS_STATEIMAGEMASK) != 0) {
            // Checkbox uses the STATEIMAGE: 1 for unchecked, 2 for checked.
            // Checkbox is checked when (old/new-state & state-image-mask) == INDEXTOSTATEIMAGEMASK(2).

            bool oldChecked = (oldState & LVIS_STATEIMAGEMASK) == INDEXTOSTATEIMAGEMASK(2);
            bool newChecked = (newState & LVIS_STATEIMAGEMASK) == INDEXTOSTATEIMAGEMASK(2);

            if (oldChecked && !newChecked && index == mSelectedIndex) {
                mSelectedIndex = -1;
                adjustButtons();
            } else if (!oldChecked && newChecked && index != mSelectedIndex) {
                // Uncheck any checked rows if any
                for (int n = mPathsListCtrl.GetItemCount() - 1; n >= 0; --n) {
                    if (n != index && mPathsListCtrl.GetCheck(n)) {
                        mPathsListCtrl.SetCheck(n, FALSE);
                    }
                }

                mSelectedIndex = index;
                adjustButtons();
            }
            // We handled this case, don't dispatch it further
            *pResult = TRUE;
        }
    }
}

// -----

const CJavaPath& CFindJava2Dlg::getSelectedPath() {
    int i = 0;
    for (const CJavaPath &p : mPaths) {
        if (i == mSelectedIndex) {
            return p;
        }
        ++i;
    }

    return CJavaPath::sEmpty;
}


void CFindJava2Dlg::fillPathsList() {
    mPathsListCtrl.DeleteAllItems();
    int index = 0;

    for (const CJavaPath& pv : mPaths) {
        mPathsListCtrl.InsertItem(index, pv.getVersion());        // column 0 = version
        mPathsListCtrl.SetItemText(index, COL_PATH, pv.mPath);    // column 1 = path
        mPathsListCtrl.SetCheck(index, mSelectedIndex == index);
        ++index;
    }
}

// Checks the given index if valid. Unchecks all other items.
//
// If index >= 0, it is used to select that item from the ListControl.
// Otherwise if path != nullptr, it is used to find the item and select it.
//
// Side effect: in both cases, mSelectedIndex is set to the matching index or -1.
//
// If index is invalid and path isn't in the mPaths list, all items are unselected
// so calling this with (0, nullptr) will clear the current selection.
void CFindJava2Dlg::selectPath(int index, const CJavaPath *path) {

    const CJavaPath *foundPath;
    // If index is not defined, find the given path in the internal list.
    // If path is not defined, find its index in the internal list.
    int i = 0;
    int n = mPathsListCtrl.GetItemCount();
    for (const CJavaPath &p : mPaths) {
        if (index < 0 && path != nullptr && p == *path) {
            index = i;
            foundPath = path;
        } else if (index == i) {
            foundPath = &p;
        }

        // uncheck any marked path
        if (i != index && i < n && mPathsListCtrl.GetCheck(i)) {
            mPathsListCtrl.SetCheck(i, FALSE);
        }

        ++i;
    }

    mSelectedIndex = index;
    if (index >= 0 && index <= n) {
        mPathsListCtrl.SetCheck(index, TRUE);
    }

    adjustButtons();
}

void CFindJava2Dlg::adjustButtons() {
    int n = mPathsListCtrl.GetItemCount();
    mOkButton.EnableWindow(mSelectedIndex >= 0 && mSelectedIndex < n);
}