/*****************************************************************************
Open Source MIT License

Copyright (c) 2017 Matthew Ahmed

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*****************************************************************************/

// MatchDlg.cpp: implementation of the CMatchDlg class.
//
//////////////////////////////////////////////////////////////////////

#include "Headers.h"
#include "resource.h"
#include "Settings.h"
#include "MatchDlg.h"
//#include "JpgFolders.h"
#include "Picture.h"
#include ".\matchdlg.h"

#define WMU_UNCHECK WMU0 + 1

CMatchDlg * pMatchDlg;


//BOOL MatchDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
//{
//	return pMatchDlg->OwnProc(hDlg, message, wParam, lParam);
//}


INT CALLBACK LvCompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
  return pMatchDlg->SortListview(lParam1, lParam2);
}

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CMatchDlg::CMatchDlg(CJpgFolders * pJpf, LPCTSTR sSrcJpg, LPCTSTR sTgtJpg,
                     HINSTANCE hInst, CSettings * pSet,
                     LPCTSTR sCapt1, LPCTSTR sCapt2,
                     CWnd * pOwner, ZWinAppDataClone * pzwd)
								// LPCTSTR sCapt1, LPCTSTR sCapt2, CZWin * pOwner)
	//: ZDlg(hInst, sCapt1, sCapt2, pOwner)
  : CZDialog(IDD_MATCH, sCapt1, pOwner, pSet, pzwd)
{
	ini = pSet;
	pMatchDlg = this; // Global for LvComparFunc
//	m_pProc = MatchDlgProc;
//	m_uID = IDD_MATCH;
  m_pJpf = pJpf;
//  gpJpf = pJpf;

  _tcscpy(m_sSrcJpg, sSrcJpg);
  _tcscpy(m_sTgtJpg, sTgtJpg);

  m_nLvSortCol = 0;
  m_nLvSort2nd = 1;
//  m_nLvSortDirection = 1;
  m_nLvSortDirectionText = 1;
  m_nLvSortDirectionColor = 1;
  m_bReviewChecks = FALSE; // Turn on after OnInitDialog. Off for batch updates.

  // ZDialog insert
  m_csCaptMain.Format(_T("%s  -  %s"), sCapt1, sCapt2);
  m_hInst = hInst;

  // CPicture
  m_pPicSrc = NULL;
  m_pPicTgt = NULL;
  m_nLvSelected = -1;
  m_bImgSrc = FALSE;
  m_bImgTgt = FALSE;

  // Hacks for CDialog with parent not a CWnd:
  //m_hInst = hInst;
  //afxCurrentResourceHandle = hInst;
  //afxCurrentInstanceHandle = hInst;
  //m_hParent = hParent;

  // Set these on OK, info for CCopyDlg
  m_nCountToCopy = 0;
  for (INT i = 0; i < MAX_ALBUMS; i++) m_nArrToCopy[i] = -1;

}


/**************************************************************
 ZDialog insert - use message map instead of OwnProc
***************************************************************/
BEGIN_MESSAGE_MAP(CMatchDlg, CDialog)
	ON_BN_CLICKED(IDCM_BN_HELP, OnBnHelp)
	ON_BN_CLICKED(IDCM_BN_SALL, OnBnCheckAll)
	ON_BN_CLICKED(IDCM_BN_DALL, OnBnUnCheckAll)
	ON_BN_CLICKED(IDCM_BN_SCOL0, OnBnCheckCol0)
	ON_BN_CLICKED(IDCM_BN_SCOL1, OnBnCheckCol1)
	ON_BN_CLICKED(IDCM_BN_SCOL2, OnBnCheckCol2)
	ON_BN_CLICKED(IDCM_BN_SCOL3, OnBnCheckCol3)

  ON_BN_CLICKED(IDCM_CK_HIDE_RED, OnCkHideRed)
  ON_BN_CLICKED(IDCM_CK_HIDE_NO_SRC, OnCkHideNoSrc)
  ON_BN_CLICKED(IDCM_CK_HIDE_NO_TGT, OnCkHideNoTgt)
  ON_BN_CLICKED(IDCM_CK_HIDE_NO_IMG, OnCkHideNoImg)

  ON_WM_CLOSE()
  ON_WM_PAINT()
  ON_WM_TIMER()
  ON_MESSAGE(WMU_UNCHECK, OnWmuUncheck)
  ON_NOTIFY(LVN_COLUMNCLICK, IDC_LV_DIRS, OnLvColumnClick)
  ON_NOTIFY(LVN_ITEMCHANGING, IDC_LV_DIRS, OnLvItemChanging)
  ON_NOTIFY(LVN_ITEMACTIVATE, IDC_LV_DIRS, OnLvItemActivated)
//  ON_NOTIFY(LVN_HOTTRACK, IDC_LV_DIRS, OnLvHotTrack)
//  ON_NOTIFY(LVN_HOTTRACK, IDC_LV_DIRS, OnLvGarbageNotify)
//  ON_NOTIFY(LVN_KEYDOWN, IDC_LV_DIRS, OnLvGarbageNotify)
  ON_NOTIFY(LVN_KEYDOWN, IDC_LV_DIRS, OnLvKeyDown)
END_MESSAGE_MAP()


LRESULT CMatchDlg::OnWmuUncheck(WPARAM wP, LPARAM)
{
  ListView_SetItemState(m_hList, wP, 0, LVIS_STATEIMAGEMASK);
  return 0;
}

void CMatchDlg::OnLvColumnClick(NMHDR *pNMHDR, LRESULT *pResult)
{
  *pResult = 0; // all cases
  LPNMLISTVIEW pnmv = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);

  // Sort by whichever column was clicked
  if (pnmv->iSubItem != m_nLvSortCol)
  {
    // Changed column. Sort ascending by new column
    // If new primary sort is col 0 (match level color), last used text is secondary
    if (pnmv->iSubItem == 0) m_nLvSort2nd = m_nLvSortCol;
    // else text sort will always break tie w/o checking match level

    m_nLvSortCol = pnmv->iSubItem;
    //m_nLvSortDirection = 1;

    // Only set sort direction of type clicked (text or match), leave other same
    if (m_nLvSortCol == 0) m_nLvSortDirectionColor = 1;
    else m_nLvSortDirectionText = 1;
  }
  else //m_nLvSortDirection *= -1; // Same column, reverse direction
  {
    if (m_nLvSortCol == 0) m_nLvSortDirectionColor *= -1;
    else m_nLvSortDirectionText *= -1;
  }

  ListView_SortItemsEx(m_hList, LvCompareFunc, 0);
}

void CMatchDlg::OnLvItemChanging(NMHDR *pNMHDR, LRESULT *pResult)
{
  *pResult = 0; // might be reset
  if (!m_bReviewChecks) return;
  LPNMLISTVIEW pnmv = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);

  // Block if checkbox is being set checked. Setting unchecked or not checkbox OK.
  // #10 at http://www.celticwolf.com/FAQs/CListCtrl_FAQ.html
  // Modified (">" vs. "-") works, so take his word for it on the shift
  // 1 is unchecked, 2 is checked. Apparently 0 is selection state, so "- 1" => TRUE
//  if (!pnmv->uNewState || !(((pnmv->uNewState & LVIS_STATEIMAGEMASK) >> 12) - 1) ) 
  // CHANGE:
  // Catch > 0, i.e. either check or uncheck will draw state image, so remove it
  if (!pnmv->uNewState || !(((pnmv->uNewState & LVIS_STATEIMAGEMASK) >> 12) > 0) ) 
    return; // with *pResult = 0;

  // If here box is being checked/unchecked. See if it's a row with unmatched folder
  static LVITEM lvi;
  lvi.iSubItem = 0;
  lvi.mask = LVIF_PARAM;
  lvi.iItem = pnmv->iItem;

  ListView_GetItem(m_hList, &lvi);

//  if (m_pJpf->GetMatchLevel(lvi.lParam) == 5) // Grey traffic light
  if (m_pJpf->GetColorLevel(lvi.lParam) == 5) // Grey traffic light
  {
    //::PostMessage(m_hWnd, WMU_UNCHECK, pnmv->iItem, 0); // Only PostMessage works
    *pResult = 1; // Supposedly should prevent default handling (showing checkbox)
  }

  // return with maybe pResult set to 1
}

void CMatchDlg::OnLvItemActivated(NMHDR *pNMHDR, LRESULT *pResult)
{
  *pResult = 0; // always

  LPNMITEMACTIVATE pnmia = (LPNMITEMACTIVATE) pNMHDR;
  //SetImagesForListItem(pnmia->iItem);
  m_nLvSelected = pnmia->iItem;
  SetPicsForListItem();
}

void CMatchDlg::OnLvKeyDown(NMHDR*, LRESULT *pResult)
{
  *pResult = 0; // always

  // Could be up, down, page down, etc. Check whatever it is.
  // LVN_KEYDOWN doesn't give iItem. Manually determine what selection is.
  INT nItem = ListView_GetSelectionMark(m_hList);
  if (nItem == m_nLvSelected || nItem == -1)
  {
    // Up/down arrow etc. key has lag before selection. Won't get new msg when
    // selection  does change, so recheck after an interval.
    SetTimer(GetRandomUint(RAND_MAX), 100, NULL); // typical lag
    SetTimer(GetRandomUint(RAND_MAX), 300, NULL); // just in case slow
  }

  m_nLvSelected = nItem;
  SetPicsForListItem();
}

void CMatchDlg::OnTimer(UINT nIDEvent)
{
  KillTimer(nIDEvent);

  INT nItem = ListView_GetSelectionMark(m_hList);
  if (nItem != m_nLvSelected || nItem != -1)
  {
    m_nLvSelected = nItem;
    SetPicsForListItem();
  }
  // else probably already handled
}

// Replace OwnProc WM_DESTROY handler (should have been WM_CLOSE in first place)
void CMatchDlg::OnClose() 
{
  m_bReviewChecks = FALSE;
  CDialog::OnClose();
}

/***************************************************************
 ZDialog insert - End message map and functions added for it
****************************************************************/


CMatchDlg::~CMatchDlg()
{
  if (m_pPicSrc) delete m_pPicSrc;
  if (m_pPicTgt) delete m_pPicTgt;
}

BOOL CMatchDlg::OnInitDialog()
{
  CZDialog::OnInitDialog();
  ::SendMessage(m_hWnd, WM_SETICON, TRUE,
							(LPARAM) LoadIcon(m_hInst, MAKEINTRESOURCE(IDI_MAIN)));

  HBITMAP hBmp;

  hBmp = LoadBitmap(m_hInst, MAKEINTRESOURCE(IDB_COL0));
  if (!hBmp) fnErr(_T("Error loading bitmap for col0 button"));
  SendDlgItemMessage(IDCM_BN_SCOL0, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM) hBmp);

  hBmp = LoadBitmap(m_hInst, MAKEINTRESOURCE(IDB_COL0_1));
  if (!hBmp) fnErr(_T("Error loading bitmap for col1 button"));
  SendDlgItemMessage(IDCM_BN_SCOL1, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM) hBmp);

  hBmp = LoadBitmap(m_hInst, MAKEINTRESOURCE(IDB_COL0_2_BN));
  if (!hBmp) fnErr(_T("Error loading bitmap for col2 button"));
  SendDlgItemMessage(IDCM_BN_SCOL2, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM) hBmp);

  hBmp = LoadBitmap(m_hInst, MAKEINTRESOURCE(IDB_COL0_3));
  if (!hBmp) fnErr(_T("Error loading bitmap for col3 button"));
  SendDlgItemMessage(IDCM_BN_SCOL3, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM) hBmp);

  // Set some text fields per image names
  csBS.Format(_T("Target folder does not have %s file.\nBright green is most exact ")
              _T("match between source and target folder names, yellow least exact."),
              m_sTgtJpg);
  SetDlgItemText(IDCM_TX_COL0_2, csBS);

  csBS.Format(_T("Target folder already has %s file.\nOrange if %s in source ")
              _T("folder has different file size, red if file sizes are the same."),
              m_sTgtJpg, m_sSrcJpg);
  SetDlgItemText(IDCM_TX_COL3_4, csBS);

  csBS.Format(_T("No matching source folder was found for target folder or vice versa;")
              _T(" or matching source and target folders were found, but source has no")
              _T(" %s file.  Checkbox to copy is not available."),
              m_sSrcJpg);
  SetDlgItemText(IDCM_TX_COL5, csBS);

  csBS.Format(_T("No \"%s\" in source folder"), m_sSrcJpg);
  SetDlgItemText(IDCM_CK_HIDE_NO_IMG, csBS);

  //csBS.Format("%s in source folder", m_sSrcJpg);
  SetDlgItemText(IDCM_TX_IMGN_SRC, m_sSrcJpg);

  //csBS.Format("%s in target folder", m_sTgtJpg);
  SetDlgItemText(IDCM_TX_IMGN_TGT, m_sTgtJpg);

  /*************************************************************************
    Begin init for CImages
  *************************************************************************/
  m_hImgSrc = GetDlgItem(IDCM_IMG_SRC)->m_hWnd;
  m_hImgTgt = GetDlgItem(IDCM_IMG_TGT)->m_hWnd;

  CRect crDlg;
  CRect crClient;
	GetWindowRect(&crDlg);
	GetClientRect(&crClient);
	m_pntOffset.x = ( crClient.Width() - crDlg.Width() ) / 2; // - border width
	m_pntOffset.y = ( crClient.Height() - crDlg.Height() )
                   - m_pntOffset.x;                           // title + border

  // Get size and location of image controls before resized with real images
  // Get upper left of thumb client units. m_crDlg set above.  Reuse crClient.
  ::GetWindowRect(m_hImgSrc, &crClient); // desktop units
  m_pntSrc = crClient.TopLeft() - crDlg.TopLeft() + m_pntOffset;

  ::GetWindowRect(m_hImgTgt, &crClient); // desktop units
  m_pntTgt = crClient.TopLeft() - crDlg.TopLeft() + m_pntOffset;

//  m_pntImgWidthHeight.x = crClient.Width(); // Same for both images
//  m_pntImgWidthHeight.y = crClient.Height();
  m_pntImgWidthHeight.x = 74; // Same for both images
  m_pntImgWidthHeight.y = 74;

  //csBS.Format("Height %d  Width %d", m_pntImgWidthHeight.x, m_pntImgWidthHeight.y);
  //MB(csBS);

//  SetImagesForListItem(0); // First item is selected on start up
//  SetPicsForListItem(0); // First item is selected on start up

  /****************************
    Change to CPicture
  *****************************/

  // For CPicture::DrawPic, dont draw over image border
  m_cpZero.SetPoint(0, 0);
  m_cszHtWd.SetSize(m_pntImgWidthHeight.x - 4, m_pntImgWidthHeight.y - 4);

  ::MoveWindow(m_hImgSrc, m_pntSrc.x, m_pntSrc.y,
                m_pntImgWidthHeight.x, m_pntImgWidthHeight.y, TRUE);

  ::MoveWindow(m_hImgTgt, m_pntTgt.x, m_pntTgt.y,
                m_pntImgWidthHeight.x, m_pntImgWidthHeight.y, TRUE);

  m_pPicSrc = new CPicture();
  if (!m_pPicSrc) fnErr(_T("Error constructing CPicture for source preview"));
  m_pPicTgt = new CPicture();
  if (!m_pPicTgt) fnErr(_T("Error constructing CPicture for target preview"));

  /*************************************************************************
    Finish init for CImages
  *************************************************************************/

  m_hList = GetDlgItem(IDC_LV_DIRS)->m_hWnd; // ZDialog

  if (!InitListView(m_pJpf->GetMatchParentPath(), m_pJpf->GetParentPath()))
    fnErr(_T("Error initializing ListView of source and target folders"));

  INT nFolders = m_pJpf->GetCount();
  
  for (INT i = nFolders - 1; i >= 0; i--)
  {
    if (!InsertListItem(m_pJpf->GetMatchPathRel(i), m_pJpf->GetPathRel(i),
//                        m_pJpf->GetMatchLevel(i), i))
                        m_pJpf->GetColorLevel(i), i))
    {
      fnErr(_T("Error inserting match item"));
      break;
    }
  }

  ListView_SortItemsEx(m_hList, LvCompareFunc, 0);

  // Select & focus first row
  ListView_SetItemState(m_hList, 0, LVIS_FOCUSED | LVIS_SELECTED,
                              LVIS_FOCUSED | LVIS_SELECTED);

  m_bReviewChecks = TRUE;

  m_nLvSelected = 0;
  srand((UINT) GetTickCount()); // For "reasonably" unique timer id's
  SetPicsForListItem(); // First item is selected on start up

  //// Set hide row checkboxes; all unchecked if option to save last session not on.
  //ini->SetDlg(m_hWnd); // Move to OnOk
  //if (!ini->DlgCheckFromKey(ini->bArtHideRed && ini->bArtSaveHide,
  //                          IDCM_CK_HIDE_RED) ||
  //    !ini->DlgCheckFromKey(ini->bArtHideNoSrc && ini->bArtSaveHide,
  //                          IDCM_CK_HIDE_NO_SRC) ||
  //    !ini->DlgCheckFromKey(ini->bArtHideNoTgt && ini->bArtSaveHide,
  //                          IDCM_CK_HIDE_NO_TGT) ||
  //    !ini->DlgCheckFromKey(ini->bArtHideNoImg && ini->bArtSaveHide,
  //                          IDCM_CK_HIDE_NO_IMG) )
  //{
  //  ini->ShowError(m_csCapt, "Non-fatal error applying ini file settings to checkboxes");
  //}

  // If using hide row settings from last session, set checkboxes & hide rows
  if (ini->bArtSaveHide)
  {
    if (ini->bArtHideRed)
    {
      CheckDlgButton(IDCM_CK_HIDE_RED, 1);
      HideRows(4);
    }
    if (ini->bArtHideNoSrc)
    {
      CheckDlgButton(IDCM_CK_HIDE_NO_SRC, 1);
      HideRows(MATCH_LEVEL_GRAY_NO_SRC);
    }
    if (ini->bArtHideNoTgt)
    {
      CheckDlgButton(IDCM_CK_HIDE_NO_TGT, 1);
      HideRows(MATCH_LEVEL_GRAY_NO_TGT);
    }
    if (ini->bArtHideNoImg)
    {
      CheckDlgButton(IDCM_CK_HIDE_NO_IMG, 1);
      HideRows(MATCH_LEVEL_GRAY_NO_IMG);
    }

  } // if (ini->bArtSaveHide)
  // else leave all checkboxes default unchecked, show all rows

  return TRUE; // ZDialog
}

void CMatchDlg::OnPaint()
{
  PAINTSTRUCT ps;
  if (m_bImgSrc) DrawPic(IDCM_IMG_SRC);
  if (m_bImgTgt) DrawPic(IDCM_IMG_TGT);
  CDC* pDC = BeginPaint(&ps);
  EndPaint(&ps);
}

void CMatchDlg::SetPicsForListItem()
{
  CString csPath;
  CString csText;
  LVITEM lvi;
  lvi.iItem = m_nLvSelected;
  lvi.mask = LVIF_PARAM; // lvi.lParam is index of entry in CJpgFolders
  lvi.iSubItem = 0;

  ListView_GetItem(m_hList, &lvi);

  /**************************************
    SOURCE FOLDER
  ***************************************/

  //csPath = m_pJpf->GetMatchJpgPath(lvi.lParam , m_sSrcJpg); // Source
  //if (csPath.Find('<') != -1)
  //{
  if (m_pJpf->GetMatchJpgBytes(lvi.lParam) <= 0) // Sometimes true if match level 5
  {
    // Target album folder in this row has no matching source folder,
    // or source folder (with or without matching target) has no album art file
    m_bImgSrc = FALSE;
    ::ShowWindow(m_hImgSrc, SW_HIDE);
    SetDlgItemText(IDCM_TX_IMGB_SRC, _T("No"));
    SetDlgItemText(IDCM_TX_IMGP_SRC, _T("file"));
  }
  else
  {
    csPath = m_pJpf->GetMatchJpgPath(lvi.lParam , m_sSrcJpg);
    if (!m_pPicSrc->Load(csPath)) fnErr(_T("Error loading source image"), csPath);
    else
    {
      m_bImgSrc = TRUE;
      DrawPic(IDCM_IMG_SRC);
    }
    ::ShowWindow(m_hImgSrc, SW_SHOW);
    //else MB("Loaded", csPath);

    csText.Format(_T("%d Bytes"), m_pJpf->GetMatchJpgBytes(lvi.lParam));
    SetDlgItemText(IDCM_TX_IMGB_SRC, csText);
    if (GetImageDimensions(csPath, csText))
      SetDlgItemText(IDCM_TX_IMGP_SRC, csText); // e.g. "200x200 Pixels"
  }

  /**************************************
    TARGET FOLDER
  ***************************************/

  if (m_pJpf->GetJpgBytes(lvi.lParam) > 0)
  {
    // Image in target folder for these match levels only
    csPath = m_pJpf->GetJpgPath(lvi.lParam, m_sTgtJpg);
    if (!m_pPicTgt->Load(csPath)) fnErr(_T("Error loading target image"), csPath);
    else
    {
      m_bImgTgt = TRUE;
      DrawPic(IDCM_IMG_TGT);
    }

    ::ShowWindow(m_hImgTgt, SW_SHOW);

    csText.Format(_T("%d Bytes"), m_pJpf->GetJpgBytes(lvi.lParam));
    SetDlgItemText(IDCM_TX_IMGB_TGT, csText);
    if (GetImageDimensions(csPath, csText))
      SetDlgItemText(IDCM_TX_IMGP_TGT, csText);
  }
  else
  {
    m_bImgTgt = FALSE;
    ::ShowWindow(m_hImgTgt, SW_HIDE);
    SetDlgItemText(IDCM_TX_IMGB_TGT, _T("No"));
    SetDlgItemText(IDCM_TX_IMGP_TGT, _T("file"));
  }


} // void CMatchDlg::SetPicsForListItem(INT nItem)

BOOL CMatchDlg::DrawPic(INT nID)
{
  BOOL bSource = (nID == IDCM_IMG_SRC);
  LPTSTR sName = bSource ? _T("source") : _T("target");
  HWND hWnd = bSource ? m_hImgSrc : m_hImgTgt;
  CPicture * pPic = bSource ? m_pPicSrc : m_pPicTgt;

  HDC hDC = ::GetDC(hWnd);
  if (!hDC)
  {
    csBS.Format(_T("GetDC failed for %s image"), sName);
    return fnErr(csBS);
  }

  CDC cDC;
  if (!cDC.Attach(hDC))
  {
    ::ReleaseDC(hWnd, hDC);
    csBS.Format(_T("cDC.Attach(hDC) failed for %s image"), sName);
    return fnErr(csBS);
  }

  BOOL bRet = pPic->Draw(&cDC, m_cpZero, m_cszHtWd);

  cDC.Detach();
  ::ReleaseDC(hWnd, hDC);

  if (!bRet) return fnErr(_T("CPicture::Draw failed"));
  return TRUE;
}

BOOL CMatchDlg::GetImageDimensions(LPCTSTR sPath, CString & csPixels)
{
  static CImage img;
  HRESULT hres = img.Load(sPath);
  if (hres != S_OK)
    return fnErr(_T("Error getting image info on previously found file"), sPath);

  csPixels.Format(_T("%dx%d Pixels"), img.GetWidth(), img.GetHeight());
  img.Destroy();

  return TRUE;
}

BOOL CMatchDlg::InitListView(LPCTSTR sSource, LPCTSTR sTarget)
{
  HBITMAP hBmp = LoadBitmap(m_hInst, MAKEINTRESOURCE(IDB_COLORS));
  if (!hBmp) fnErr(_T("Error loading bitmap"));

  HIMAGELIST hIml = ImageList_Create(16, 16, ILC_COLOR8, 32, 32);
  if (!hIml) fnErr(_T("Error creating image list"));
  
//  ImageList_AddMasked(hImageList,hBitmap,RGB(0,255,255));
  ImageList_Add(hIml, hBmp, 0);
  DeleteObject(hBmp);

  ListView_SetImageList(m_hList, hIml, LVSIL_SMALL);

	LVCOLUMN lvc;
	TCHAR sHdr[128];
	//ListView_SetExtendedListViewStyle(m_hList,
 //     //  LVS_SHOWSELALWAYS |
 //       LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP); // | LVS_EX_GRIDLINES);

	ListView_SetExtendedListViewStyleEx(m_hList,
    //LVS_EX_TRACKSELECT |
    LVS_EX_ONECLICKACTIVATE |
    LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP, // What style to set
    //LVS_EX_TRACKSELECT |
    LVS_EX_ONECLICKACTIVATE |
    LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP); // What to set it to

	// Normalize column width, specified in raw pixels, for resolution & font
	INT nBaseWidth = LOWORD(GetDialogBaseUnits()); // avg pixels per char

	// Initialize the LVCOLUMN structure.
	// The mask specifies that the format, width, text, and
	// subitem members of the structure are valid. 
	
//	lvc.fmt = LVCFMT_LEFT; // left aligned, all columns

	// Set columns
	lvc.pszText = sHdr;	   // always same string pointer, text changes

	//lvc.iSubItem = 0;  // Same as col index param in ListView_InsertColumn
	//_tcscpy(sHdr, "");
	//lvc.cx = 5 * nBaseWidth;     // width of column in pixels
	//if (ListView_InsertColumn(m_hList, lvc.iSubItem, &lvc) == -1) 
	//	return FALSE;

//	lvc.mask = LVCF_FMT | LVCF_WIDTH |  LVCF_SUBITEM | LVCF_TEXT | LVCFMT_BITMAP_ON_RIGHT;
	lvc.mask = LVCF_FMT | LVCF_WIDTH |  LVCF_SUBITEM | LVCF_TEXT;

  lvc.fmt = LVCFMT_LEFT;

	lvc.iSubItem = 0; 
//  _stprintf(sHdr, "Copy from %s\\...", sSource);
  _tcscpy(sHdr, _T("Match"));
	lvc.cx = 6 * nBaseWidth; 
//	lvc.cx = 400; 
//	lvc.cx = 50; 
	if (ListView_InsertColumn(m_hList, lvc.iSubItem, &lvc) == -1) 
		return FALSE;


	lvc.iSubItem ++;
//	lvc.iSubItem = 0; 
  _stprintf(sHdr, _T("Copy %s from %s\\..."), m_sSrcJpg, sSource);
//	lvc.cx = 49 * nBaseWidth; 
//	lvc.cx = 350;
  INT nSongPathWidth =  (INT) (42.5 * (FLOAT) nBaseWidth); // Same width for source and target
  lvc.cx = nSongPathWidth;
	if (ListView_InsertColumn(m_hList, lvc.iSubItem, &lvc) == -1) 
		return FALSE;


	lvc.iSubItem ++;
  //_tcscpy(sHdr, "to F:\\MUSIC\\...");
  _stprintf(sHdr, _T("to %s\\... as %s"), sTarget, m_sTgtJpg);
//	lvc.cx = 45 * nBaseWidth; 
//	lvc.cx = 390;
  lvc.cx = nSongPathWidth;

	return (ListView_InsertColumn(m_hList, lvc.iSubItem, &lvc) != -1);
}

BOOL CMatchDlg::InsertListItem(LPCTSTR sSource, LPCTSTR sTarget,
                               INT nImg, INT nIxJpf)
{
	LVITEM lvi;
	TCHAR sTxt[1024];

	// Initialize LVITEM members that are common to all items. 
	lvi.iItem = 0; // Where in list before sorting, 0/first always safe

  // For sorting list need
//	lvi.mask = LVIF_TEXT | LVIF_PARAM; // | LVIF_DI_SETITEM; //
//	lvi.lParam = (LPARAM) nSortIndex;

//	lvi.mask = LVIF_TEXT | LVIF_STATE | LVIF_DI_SETITEM;  // removed LVIF_IMAGE | LVIF_PARAM


//	lvi.state = 0;      // Not messing with selected etc.
//	lvi.stateMask = 0; 

	lvi.pszText = sTxt;  // Always same pointer, copy text each time
	sTxt[0] = _T('\0'); 

	lvi.iSubItem = 0;
//	_tcscpy(sTxt, sSource); 
//  lvi.mask = LVIF_IMAGE | LVIF_TEXT | LVIF_PARAM;
  lvi.mask = LVIF_IMAGE | LVIF_PARAM;
  lvi.iImage = nImg;

//  lvi.lParam = (LPARAM) nImg;
  lvi.lParam = (LPARAM) nIxJpf;

	if(ListView_InsertItem(m_hList, &lvi) == -1) return FALSE;

///////// SEPARATE FROM IMAGE ////////////////////////////////////////////
//	lvi.iSubItem = 0;
	lvi.iSubItem++;
	_tcscpy(sTxt, sSource); 
//  lvi.mask = LVIF_IMAGE | LVIF_TEXT | LVIF_PARAM;
  lvi.mask = LVIF_TEXT;
//  lvi.iImage = nImg;

//  lvi.lParam = (LPARAM) nIxJpf;

	if(ListView_SetItem(m_hList, &lvi) == -1) return FALSE;



	// One Sub Item
	lvi.iSubItem ++;
	lvi.mask = LVIF_TEXT;
  // For no-match items hack, CJpgFolders has '\' at start of sPathRel
  if (sTarget[0] == _T('\\')) _tcscpy(sTxt, &sTarget[1]);
	else _tcscpy(sTxt, sTarget);

	if(ListView_SetItem(m_hList, &lvi) == -1) return FALSE;

  if (nImg == 5) // Greyed image, unmatched folder
  {
    // Remove checkbox
    ListView_SetItemState(m_hList, 0, 0, LVIS_STATEIMAGEMASK);
  }

	return TRUE;
} // BOOL CMatchDlg::InsertListItem(LPCTSTR sSource, LPCTSTR sTarget, INT nImg)

INT CMatchDlg::SortListview(LPARAM lp1, LPARAM lp2)
{
  if (m_nLvSortCol == 0) return SortListviewCol0(lp1, lp2);
  else return SortListviewTextCol(lp1, lp2, m_nLvSortCol);
} // INT CMatchDlg::SortListview(LPARAM lp1, LPARAM lp2)

INT CMatchDlg::SortListviewCol0(LPARAM lp1, LPARAM lp2)
{
  // Sort by match level (color). Secondary sort; last text col to be primary
  static LVITEM lvi1;
  static LVITEM lvi2;
  static INT nSort;

  lvi1.iSubItem = 0;
  lvi1.mask = LVIF_PARAM;
//  lvi2.iSubItem = 1;   // HUH? Should it be 0 but doesn't matter for lvi.lParam? ///////////////////////////////
  lvi2.iSubItem = 0;
  lvi2.mask = LVIF_PARAM;

  // Only part that varies:
  lvi1.iItem = lp1;
  lvi2.iItem = lp2;

  ListView_GetItem(m_hList, &lvi1);
  ListView_GetItem(m_hList, &lvi2);

//  // First rank in order of match levels (traffic light colors)
//	nSort = m_pJpf->GetMatchLevel(lvi1.lParam) - m_pJpf->GetMatchLevel(lvi2.lParam);

  // First rank in order of traffic light colors (all gray match levels ranked equal)
	nSort = m_pJpf->GetColorLevel(lvi1.lParam) - m_pJpf->GetColorLevel(lvi2.lParam);
//  if (nSort) return nSort * m_nLvSortDirection; // times 1, or -1 for reverse
  if (nSort) return nSort * m_nLvSortDirectionColor; // times 1, or -1 for reverse

  // If same match level, SortListviewTextCol is in tie breaker order
  return SortListviewTextCol(lp1, lp2, m_nLvSort2nd);
} // INT CMatchDlg::SortListviewCol0(LPARAM lp1, LPARAM lp2)

INT CMatchDlg::SortListviewTextCol(LPARAM lp1, LPARAM lp2, INT nColFirst)
{
  static LVITEM lvi1;
  static LVITEM lvi2;
  static TCHAR sTxt1[MAX_PATH];
  static TCHAR sTxt2[MAX_PATH];
  static INT nSort;

  lvi1.pszText = sTxt1;
  lvi1.cchTextMax = MAX_PATH;
  lvi1.mask = LVIF_TEXT;

  lvi2.pszText = sTxt2;
  lvi2.cchTextMax = MAX_PATH;
  lvi2.mask = LVIF_TEXT;

  // The part that varies:
  lvi1.iItem = lp1;
  lvi2.iItem = lp2;
  lvi1.iSubItem = nColFirst;
  lvi2.iSubItem = nColFirst;

  ListView_GetItem(m_hList, &lvi1);
  ListView_GetItem(m_hList, &lvi2);

  nSort = ListViewStringSort(lvi1.pszText, lvi2.pszText);
//  if (nSort) return nSort * m_nLvSortDirection; // times 1, or -1 for reverse
  if (nSort) return nSort * m_nLvSortDirectionText; // times 1, or -1 for reverse

  // Tied. Next sort on whichever column, 1 or 2 wasnt tried above
  INT nNextCol = (nColFirst == 1) ? 2 : 1;

  lvi1.iSubItem = nNextCol;
  lvi2.iSubItem = nNextCol;

  ListView_GetItem(m_hList, &lvi1);
  ListView_GetItem(m_hList, &lvi2);

//  return m_nLvSortDirection * ListViewStringSort(lvi1.pszText, lvi2.pszText);
  return m_nLvSortDirectionText * ListViewStringSort(lvi1.pszText, lvi2.pszText);
} // INT CMatchDlg::SortListviewTextCol(LPARAM lp1, LPARAM lp2, INT nColFirst)

INT CMatchDlg::ListViewStringSort(LPCTSTR s1, LPCTSTR s2)
{
  // Sort in alpha order except "<....>" lines sort last
  if (s1[0] == _T('<'))
  {
    if (s2[0] == _T('<')) return 0;
    return 1;
  }

  if (s2[0] == _T('<')) return -1;

  return _tcsicmp(s1, s2);
}

void CMatchDlg::SetCheckStateAll(BOOL bCheck)
{
  m_bReviewChecks = FALSE;

  INT nRows = ListView_GetItemCount(m_hList);
  LVITEM lvi;
  // Same every loop cycle:
  lvi.iSubItem = 0;
  lvi.mask = LVIF_PARAM;

  for (INT i = 0; i < nRows; i++)
  {
    lvi.iItem = i;
    ListView_GetItem(m_hList, &lvi);
     // Ignore unmatched folders (grey)
//    if (m_pJpf->GetMatchLevel(lvi.lParam) == 5) continue;
    if (m_pJpf->GetColorLevel(lvi.lParam) == 5) continue;

    ListView_SetCheckState(m_hList, i, bCheck);
  }

  m_bReviewChecks = TRUE; // Intercept clicks on level 5 (grey) checkboxes
}

void CMatchDlg::SetCheckStateByColor(INT nMaxMatchLevel) // higher is worse match
{
  m_bReviewChecks = FALSE;

  INT nRows = ListView_GetItemCount(m_hList);
  LVITEM lvi;
  // Same every loop cycle:
  lvi.iSubItem = 0;
  lvi.mask = LVIF_PARAM;

  for (INT i = 0; i < nRows; i++)
  {
    lvi.iItem = i;
    ListView_GetItem(m_hList, &lvi);
//    if (m_pJpf->GetMatchLevel(lvi.lParam) == 5) continue; // Wastes processing?

    // Same result with GetColorLevel, this is less processing
    ListView_SetCheckState(m_hList, i, (m_pJpf->GetMatchLevel(lvi.lParam) <= nMaxMatchLevel));
  }

  m_bReviewChecks = TRUE; // Intercept clicks on level 5 (grey) checkboxes
}

BOOL CMatchDlg::ListFilesToCopy()
{
  INT nRows = ListView_GetItemCount(m_hList);
  INT nIxJpf;

  LVITEM lvi;
  lvi.iSubItem = 0;
  lvi.mask = LVIF_PARAM;

  m_nCountToCopy = 0;

  for (INT i = 0; i < nRows; i++)
  {
    lvi.iItem = i;
    if (!ListView_GetCheckState(m_hList, i)) continue; // not checked

    ListView_GetItem(m_hList, &lvi);
    nIxJpf = lvi.lParam;

//    if (m_pJpf->GetMatchLevel(nIxJpf) == 5) // Could happen, hack just hides the checkbox
    if (m_pJpf->GetColorLevel(nIxJpf) == 5) // Could happen, hack just hides the checkbox
      continue;

    // This file to be copied. CCopyDlg will use this array to know which
    // items in CJpegFolders to copy.
    m_nArrToCopy[m_nCountToCopy] = nIxJpf;
    m_nCountToCopy++;

  } // for (INT i = 0; i < nRows; i++)

  if (!m_nCountToCopy)
    return fnErr(_T("Nothing to copy!"), _T("No files are checked in the list."));

  return TRUE;
} // BOOL CMatchDlg::ListFilesToCopy()

void CMatchDlg::OnBnCheckAll()
{
  SetCheckStateAll(TRUE);
}

void CMatchDlg::OnBnUnCheckAll()
{
  SetCheckStateAll(FALSE);
}


void CMatchDlg::OnBnCheckCol0()
{
  SetCheckStateByColor(0);
}

void CMatchDlg::OnBnCheckCol1()
{
  SetCheckStateByColor(1);
}

void CMatchDlg::OnBnCheckCol2()
{
  SetCheckStateByColor(2);
}

void CMatchDlg::OnBnCheckCol3()
{
  SetCheckStateByColor(3);
}

void CMatchDlg::OnCkHideRed()
{
  ShowOrHideRows(IDCM_CK_HIDE_RED, 4);
}

void CMatchDlg::OnCkHideNoSrc()
{
  ShowOrHideRows(IDCM_CK_HIDE_NO_SRC, MATCH_LEVEL_GRAY_NO_SRC);
}

void CMatchDlg::OnCkHideNoTgt()
{
  ShowOrHideRows(IDCM_CK_HIDE_NO_TGT, MATCH_LEVEL_GRAY_NO_TGT);
}

void CMatchDlg::OnCkHideNoImg()
{
  ShowOrHideRows(IDCM_CK_HIDE_NO_IMG, MATCH_LEVEL_GRAY_NO_IMG);
}

BOOL CMatchDlg::ShowOrHideRows(INT nCtlId, INT nMatchLevel)
{
  return IsDlgButtonChecked(nCtlId) ? HideRows(nMatchLevel)
                                    : ShowRows(nMatchLevel);
}

BOOL CMatchDlg::HideRows(INT nMatchLevelToHide)
{
//BOOL bFirst = TRUE;
  // This fn called if checkbox to hide has just been checked
  INT nIxJpf;

  LVITEM lvi;
  lvi.iSubItem = 0; 
  lvi.mask = LVIF_PARAM;
  INT nRows = ListView_GetItemCount(m_hList);

  for (INT i = nRows - 1; i >= 0; i--)
  {
    lvi.iItem = i;
    ListView_GetItem(m_hList, &lvi);
    nIxJpf = lvi.lParam;

    if (m_pJpf->GetMatchLevel(nIxJpf) != nMatchLevelToHide) continue;

//if (bFirst)
//{
//  bFirst = FALSE;
//    csBS.Format("Error deleting item %d of %d\n", i, nRows);
//  // Get path text to identify error row
//  TCHAR sTxt[MAX_PATH]; 
//  lvi.pszText = sTxt;
//  lvi.cchTextMax = MAX_PATH;
//  lvi.mask = LVIF_TEXT;
//
//  lvi.iSubItem = 1; // Source folder
//  ListView_GetItem(m_hList, &lvi);
//  csBS += lvi.pszText;
//
//  lvi.iSubItem = 2; // Target folder
//  ListView_GetItem(m_hList, &lvi);
////  fnErr("TEST", csBS, lvi.pszText);
//  csBS2.Format("TEST\n%s\n%s", csBS, lvi.pszText);
//
//  lvi.iSubItem = 0; 
//  lvi.mask = LVIF_PARAM;
//}

    if (!ListView_DeleteItem(m_hList, i))
    {
      csBS.Format(_T("Error deleting item %d of %d\n"), i, nRows);

      // Get path text to identify error row
      TCHAR sTxt[MAX_PATH]; 
      lvi.pszText = sTxt;
      lvi.cchTextMax = MAX_PATH;
      lvi.mask = LVIF_TEXT;

      lvi.iSubItem = 1; // Source folder
      ListView_GetItem(m_hList, &lvi);
      csBS += lvi.pszText;

      lvi.iSubItem = 2; // Target folder
      ListView_GetItem(m_hList, &lvi);

      return fnErr(csBS, lvi.pszText);
    } // if (!ListView_DeleteItem(m_hList, i))

  } // for (INT i = nRows - 1; i >= 0; i--)

//fnErr(csBS2);
  return TRUE;
} // BOOL CMatchDlg::HideRows(INT nRowType)


BOOL CMatchDlg::ShowRows(INT nMatchLevelToShow)
{
//BOOL bFirst = TRUE;
  // This fn called if checkbox to hide has just been UNchecked
  INT nFolders = m_pJpf->GetCount();
  for (INT i = 0; i < nFolders; i++)
  {
    if (m_pJpf->GetMatchLevel(i) != nMatchLevelToShow) continue;

//if (bFirst)
//{
//  bFirst = FALSE;
//  csBS.Format("Error inserting item %d of %d", i, nFolders);
//  csBS2.Format("TEST\n%s\n%s\n%s", csBS, m_pJpf->GetMatchPathRel(i), m_pJpf->GetPathRel(i));
//}

    if (!InsertListItem(m_pJpf->GetMatchPathRel(i), m_pJpf->GetPathRel(i),
                        min(nMatchLevelToShow, 5), // 5 for all gray match levels
                        i))
    {
      csBS.Format(_T("Error inserting item %d of %d"), i, nFolders);
      return fnErr(csBS, m_pJpf->GetMatchPathRel(i), m_pJpf->GetPathRel(i));
    }

  } // for (INT i = 0; i < nFolders; i++)

//fnErr(csBS2);

  // New rows added out of order. Sort using current sort settings
  ListView_SortItemsEx(m_hList, LvCompareFunc, 0);

  return TRUE;
} // BOOL CMatchDlg::ShowRows(INT nCtlId)

void CMatchDlg::OnOK()
{
  // Save hide row checkstates, whether or not currently set to use saved info
  ini->SetDlg(m_hWnd);
  if (!ini->DlgCheckToKey(ini->bArtHideRed, IDCM_CK_HIDE_RED) ||
      !ini->DlgCheckToKey(ini->bArtHideNoSrc, IDCM_CK_HIDE_NO_SRC) ||
      !ini->DlgCheckToKey(ini->bArtHideNoTgt, IDCM_CK_HIDE_NO_TGT) ||
      !ini->DlgCheckToKey(ini->bArtHideNoImg, IDCM_CK_HIDE_NO_IMG) )
  {
    ini->ShowError(m_csCapt, _T("Non-fatal error saving checkbox settings for next time"));
  }

  if (!ini->WriteIniSection(ini->m_sectOptCopy))
    ini->ShowError(m_csCapt, _T("Non-fatal error saving ini settings"));

  // Set variables with info for CCopyDlg
  if (!ListFilesToCopy()) // False if no ListView items selected to copy
    return; // Let user check some, or cancel
    
  CDialog::OnOK();
}

void CMatchDlg::OnBnHelp()
{
//  if (GetOwnDirSlash(sBS))
  if (! m_pzwd->sProgDirSlash[0])
  {
    _stprintf(sBS2, _T("%sShaker.chm::/aa_match.htm"), sBS);
    ::HtmlHelp(::GetDesktopWindow(), sBS2, HH_DISPLAY_TOC, NULL) ;
  }
  else fnErr(_T("Help file location (program folder) not found"));
}


