//####COPYRIGHTBEGIN####
//                                                                          
// ----------------------------------------------------------------------------
// Copyright (C) 1998, 1999, 2000 Red Hat, Inc.
//
// This program is part of the eCos host tools.
//
// This program 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 of the License, or (at your option) 
// any later version.
// 
// This program 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 General Public License for 
// more details.
// 
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the Free Software Foundation, Inc., 
// 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// ----------------------------------------------------------------------------
//                                                                          
//####COPYRIGHTEND####
// PkgAdminDlg.cpp : implementation file
//

#include "stdafx.h"

#include "PkgAdmin.h"
#include "PkgAdminDlg.h"
#include "PkgAdminLicenseDlg.h"
#include "PkgAdminTclWaitDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CPkgadminAboutDlg dialog used for App About

class CPkgadminAboutDlg : public CDialog
{
public:
	CPkgadminAboutDlg();

// Dialog Data
	//{{AFX_DATA(CPkgadminAboutDlg)
	enum { IDD = IDD_PKGADMIN_ABOUTBOX };
	//}}AFX_DATA

	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CPkgadminAboutDlg)
	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
	//}}AFX_VIRTUAL

// Implementation
protected:
	//{{AFX_MSG(CPkgadminAboutDlg)
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
};

CPkgadminAboutDlg::CPkgadminAboutDlg() : CDialog(CPkgadminAboutDlg::IDD)
{
	//{{AFX_DATA_INIT(CPkgadminAboutDlg)
	//}}AFX_DATA_INIT
}

void CPkgadminAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CPkgadminAboutDlg)
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CPkgadminAboutDlg, CDialog)
	//{{AFX_MSG_MAP(CPkgadminAboutDlg)
		// No message handlers
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CPkgAdminDlg dialog

CPkgAdminDlg::CPkgAdminDlg(LPCTSTR pszRepository,LPCTSTR pszUserTools)
	: CeCosDialog(CPkgAdminDlg::IDD, NULL)
{
	//{{AFX_DATA_INIT(CPkgAdminDlg)
		// NOTE: the ClassWizard will add member initialization here
	//}}AFX_DATA_INIT
	// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
  if(pszRepository){
	  m_strRepository=pszRepository;
  }
  if(pszUserTools){
	  m_strUserTools=pszUserTools;
  }
	m_CdlPkgData = NULL;
}

void CPkgAdminDlg::DoDataExchange(CDataExchange* pDX)
{
	CeCosDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CPkgAdminDlg)
	DDX_Control(pDX, IDC_PKGADMIN_REMOVE, m_btnRemove);
	DDX_Control(pDX, IDC_PKGADMIN_TREE, m_ctrlPackageTree);
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CPkgAdminDlg, CeCosDialog)
	//{{AFX_MSG_MAP(CPkgAdminDlg)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_PKGADMIN_REMOVE, OnPkgadminRemove)
	ON_WM_DESTROY()
	ON_BN_CLICKED(IDC_PKGADMIN_ADD, OnPkgadminAdd)
	ON_BN_CLICKED(IDCLOSE, OnClose)
	ON_BN_CLICKED(IDC_PKGADMIN_REPOSITORY, OnPkgadminRepository)
	ON_NOTIFY(TVN_SELCHANGED, IDC_PKGADMIN_TREE, OnSelchangedPkgadminTree)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CPkgAdminDlg message handlers

BOOL CPkgAdminDlg::OnInitDialog()
{
	CeCosDialog::OnInitDialog();

  if(this==AfxGetApp()->m_pMainWnd){ // only if the dialog is the application
	  // Add "About..." menu item to system menu.

	  // IDM_ABOUTBOX must be in the system command range.
	  ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	  ASSERT(IDM_ABOUTBOX < 0xF000);

	  CMenu* pSysMenu = GetSystemMenu(FALSE);
	  if (pSysMenu != NULL)
	  {
		  CString strAboutMenu;
		  strAboutMenu.LoadString(IDS_ABOUTBOX);
		  if (!strAboutMenu.IsEmpty())
		  {
			  pSysMenu->AppendMenu(MF_SEPARATOR);
			  pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		  }
	  }

	// 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
	
  // The following AppWizard-generated call was causing the 32x32 icon to
	//  be resized for use as a 16x16 icon. Removing the call causes the
	//  correct 16x16 icon to be displayed.
//	SetIcon(m_hIcon, FALSE);		// Set small icon
	
	// TODO: Add extra initialization here

	// setup the repsoitory location

	  if (m_strRepository.IsEmpty()) // if the repository cannot be located
	  {
		  OnPkgadminRepository (); // prompt the user for the repository location
		  if (m_strRepository.IsEmpty ())
		  {
			  PostMessage (WM_COMMAND,IDCANCEL);
			  return TRUE;
		  }
	  }
  } else {
    GetDlgItem(IDC_PKGADMIN_REPOSITORY)->ShowWindow(SW_HIDE);
  }

	// setup the path to the user tools (tar and gunzip)

	if ((! m_strUserTools.IsEmpty()) || FindUserToolsPath ()) // if the user tools can be located
	{
		// add the user tools to the PATH environment variable
		const DWORD nLength = GetEnvironmentVariable (_T("PATH"), NULL, 0) + 1;
		TCHAR * pszOldPath  = new TCHAR [nLength];
		GetEnvironmentVariable (_T("PATH"), pszOldPath, nLength);
		SetEnvironmentVariable (_T("PATH"), CString (pszOldPath) + _T(";") + m_strUserTools);
		delete [] pszOldPath;
	}

	// setup the package tree image list

	m_ilTreeIcons.Create (IDB_PKGADMIN_TREEICONS, 16, 1, RGB (0,128,128));
	m_ctrlPackageTree.SetImageList (&m_ilTreeIcons, TVSIL_NORMAL);

	// populate the package tree

	while (! PopulatePackageTree (m_strRepository))
	{
		m_strRepository = _T("");
		OnPkgadminRepository (); // prompt the user for the repository location
		if (m_strRepository.IsEmpty ()) // if dialog was cancelled
		{
			PostQuitMessage (1);
			return TRUE;
		}
	}
	
	return TRUE;  // return TRUE  unless you set the focus to a control
}

void CPkgAdminDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CPkgadminAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CeCosDialog::OnSysCommand(nID, lParam);
	}
}

// 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.

void CPkgAdminDlg::OnPaint() 
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, (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
	{
		CeCosDialog::OnPaint();
	}
}

// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CPkgAdminDlg::OnQueryDragIcon()
{
	return (HCURSOR) m_hIcon;
}

bool CPkgAdminDlg::PopulatePackageTree(LPCTSTR pszPackagesPath)
{
	// delete any existing CDL database

	if (m_CdlPkgData)
	{
		delete m_CdlPkgData;
		m_CdlPkgData = NULL;
	}

	// load the package database

	try
	{
        m_CdlPkgData = CdlPackagesDatabaseBody::make (UnicodeToStdStr (pszPackagesPath));
	}
	catch (CdlStringException exception)
	{
		CString strMessage;
		strMessage.Format (_T("Error loading database:\n\n%s"), CString (exception.get_message ().c_str ()));
		AfxMessageBox (strMessage);
		return false;
	}
	catch (...)
	{
		AfxMessageBox (_T("Error loading database"));
		return false;
	}

	// clear the old package tree

	ClearPackageTree ();

	// populate the new package tree

	const std::vector<std::string>& packages = m_CdlPkgData->get_packages ();
	for (std::vector<std::string>::const_iterator package = packages.begin (); package != packages.end (); package++)
	{
		// add a package node

		TRACE (_T("Adding package %s:"), CString (package->c_str ()));
		HTREEITEM hPackage = m_ctrlPackageTree.InsertItem (CString (m_CdlPkgData->get_package_aliases (*package) [0].c_str ()));
		m_ctrlPackageTree.SetItemData (hPackage, (DWORD) new CString (package->c_str ()));
		m_ctrlPackageTree.SetItemImage (hPackage, 0, 0);

		const std::vector<std::string>& versions = m_CdlPkgData->get_package_versions (* package);
		for (std::vector<std::string>::const_iterator version = versions.begin (); version != versions.end (); version++)
		{
			// add a version node

			TRACE (_T(" %s"), CString (version->c_str ()));
			const HTREEITEM hVersion = m_ctrlPackageTree.InsertItem (CString (version->c_str ()), hPackage);
			m_ctrlPackageTree.SetItemImage (hVersion, 1, 1);
		}
		TRACE (_T("\n"));
		m_ctrlPackageTree.SortChildren (hPackage); // sort the version nodes
	}

	m_ctrlPackageTree.SortChildren (NULL); // sort the package nodes

  if(this==AfxGetApp()->m_pMainWnd){ // if the dialog is the application
	  // update the caption bar
	  CString strCaption (m_strRepository);
	  strCaption.Replace (_TCHAR('/'), _TCHAR('\\'));
	  strCaption += _T(" - eCos Package Administration Tool");
	  SetWindowText (strCaption);
  }

	return true;
}

void CPkgAdminDlg::OnPkgadminRemove() 
{
	const HTREEITEM hTreeItem = m_ctrlPackageTree.GetSelectedItem ();
	if (! hTreeItem)
		return;

	if (IDYES != CWnd::MessageBox (_T("The selected package will be deleted from the repository. Core eCos packages may be restored only by reinstalling eCos.\n\nDo you wish to continue?"),
		_T("Remove Package"), MB_YESNO | MB_ICONEXCLAMATION))
		return;

	const CString * pstrPackage = (const CString *) m_ctrlPackageTree.GetItemData (hTreeItem);
	if (pstrPackage) // if a package node is selected
	{
		// remove all package version nodes

		bool bStatus = true;
		HTREEITEM hChildItem = m_ctrlPackageTree.GetChildItem (hTreeItem);
		while (hChildItem && bStatus)
		{
			const HTREEITEM hNextChildItem = m_ctrlPackageTree.GetNextSiblingItem (hChildItem);			
			bStatus = RemovePackageVersion (hChildItem);
			hChildItem = hNextChildItem;
		}

		// remove the package node

		if (bStatus)
		{
			delete pstrPackage;
			m_ctrlPackageTree.DeleteItem (hTreeItem);
		}
	}
	else // a version node is selected
	{
		// remove the version node

		const HTREEITEM hParentItem = m_ctrlPackageTree.GetParentItem (hTreeItem);
		ASSERT (hParentItem);
		if (RemovePackageVersion (hTreeItem) && ! m_ctrlPackageTree.ItemHasChildren (hParentItem)) // if the only version was deleted
		{
			// remove the package node

			delete pstrPackage;
			m_ctrlPackageTree.DeleteItem (hParentItem); 
		}
	}
}

void CPkgAdminDlg::ClearPackageTree()
{
	HTREEITEM hPackage = m_ctrlPackageTree.GetRootItem ();
	if (! hPackage) // if no packages in the tree...
		return;     // ...nothing to do

	while (hPackage)
	{
		const HTREEITEM hNextPackage = m_ctrlPackageTree.GetNextSiblingItem (hPackage);
		TRACE (_T("Deleting package %s\n"), * ((CString *) m_ctrlPackageTree.GetItemData (hPackage)));
		delete (CString *) m_ctrlPackageTree.GetItemData (hPackage);
		m_ctrlPackageTree.DeleteItem (hPackage);
		hPackage = hNextPackage;
	}
}

void CPkgAdminDlg::OnDestroy() 
{
	CeCosDialog::OnDestroy();
	
	// free memory allocated to the tree item data CStrings

	ClearPackageTree ();

	// free memory allocated to the CDL database

	if (m_CdlPkgData)
		delete m_CdlPkgData;

}

void CPkgAdminDlg::OnPkgadminAdd() 
{
	CFileDialog dlg (TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT, _T("eCos Package Files (*.epk)|*.epk||"), GetParent ());
	TCHAR szBuffer [MAX_PATH * 16] = _T("");
	dlg.m_ofn.lpstrFile = szBuffer;
	dlg.m_ofn.nMaxFile = MAX_PATH * 16;
	dlg.m_ofn.lpstrTitle = _T("Open eCos Package Files");
	if (IDOK == dlg.DoModal ())
	{
		bool bRepositoryChanged = false;
		POSITION posPathName = dlg.GetStartPosition ();
		while (posPathName)
		{
			// get an eCos package distribution file

			CString strPathName = dlg.GetNextPathName (posPathName);

			// extract the licence file

			CString strCommand;
			strCommand.Format (_T("add %s --extract_license"), strPathName);
			strCommand.Replace (_TCHAR('\\'), _TCHAR('/')); // backslashes -> forward slashes for Tcl_EvalFile
			EvalTclFile (3, strCommand);
			CString strLicenseFile = m_strRepository + _T("/pkgadd.txt");
			strLicenseFile.Replace (_TCHAR('/'), _TCHAR('\\')); // forward slashes -> backslashes for Win32

			// read the license file

			CFile fileLicenseFile;
			if (fileLicenseFile.Open (strLicenseFile, CFile::modeRead))
			{
				TRACE (_T("License file found at %s\n"), strLicenseFile);
				const DWORD dwLicenseLength = fileLicenseFile.GetLength ();
				char * pszBuffer = new char [dwLicenseLength + 1]; // allocate a buffer
				fileLicenseFile.Read (pszBuffer, dwLicenseLength);
				fileLicenseFile.Close ();
				CFile::Remove (strLicenseFile); // delete the license file when read
				pszBuffer [dwLicenseLength] = NULL; // terminate the string in the buffer
				CString strLicenseText (pszBuffer); // copy into a CString to convert to Unicode
				delete [] pszBuffer;
				if (-1 == strLicenseText.Find (_T("\r\n"))) // if the file has LF line endings...
					strLicenseText.Replace (_T("\n"), _T("\r\n")); // ... replace with CRLF line endings

				// display the license text

				CPkgAdminLicenseDlg dlgLicense (this);
				dlgLicense.m_strLicense = strLicenseText;
				dlgLicense.SetCaption (strPathName + _T(" - Add Packages"));
				if (IDOK != dlgLicense.DoModal ()) // if license not accepted by user
					continue; // try the next file
			}

			// add the contents of the package distribution file

			strCommand.Format (_T("add %s --accept_license"), strPathName);
			strCommand.Replace (_TCHAR('\\'), _TCHAR('/')); // backslashes -> forward slashes for Tcl_EvalFile
			if (! EvalTclFile (3, strCommand))  // if not successful
				continue; // try the next file

			bRepositoryChanged = true;
		}

		// refresh the package tree only if necessary

		if (bRepositoryChanged && ! PopulatePackageTree (m_strRepository))
			DestroyWindow ();
	}
}

bool CPkgAdminDlg::EvalTclFile(int nArgc, LPCTSTR pszArgv)
{
	CPkgAdminTclWaitDlg dlgWait;

	TRACE (_T("Evaluating ecosadmin.tcl %s\n"), pszArgv);

	// set up the data structure which is passed to the Tcl thread

	CString strArgc;
	strArgc.Format (_T("%d"), nArgc);
	std::string argv0 = UnicodeToStdStr (m_strRepository) + "/ecosadmin.tcl";
	std::string argv = UnicodeToStdStr (pszArgv);
	std::string argc = UnicodeToStdStr (strArgc);
	dlgWait.etsInfo.argv0 = (char *) argv0.c_str ();
	dlgWait.etsInfo.argv = (char *) argv.c_str ();
	dlgWait.etsInfo.argc = (char *) argc.c_str ();

	// display the 'please wait' dialog
	// the Tcl command is invoked from CPkgAdminTclWaitDlg::OnCreate()

	CWaitCursor curWait;
	dlgWait.DoModal ();
	curWait.Restore ();

	// retrieve status information from the data structure

	int nStatus = dlgWait.etsInfo.status;
	CString strErrorMessage (dlgWait.etsInfo.result);

	// report any error

	if (! strErrorMessage.IsEmpty ())
	{
		AfxMessageBox (_T("Command execution error:\n\n") + strErrorMessage);
		return false;
	}
	else if (TCL_OK != nStatus)
	{
		AfxMessageBox (_T("Command execution error"));
		return false;
	}

	return true;
}

bool CPkgAdminDlg::RemovePackageVersion(HTREEITEM hTreeItem)
{
	const HTREEITEM hParentItem = m_ctrlPackageTree.GetParentItem (hTreeItem);
	ASSERT (hParentItem);
	CString * pstrPackage = (CString *) m_ctrlPackageTree.GetItemData (hParentItem);
	ASSERT (pstrPackage);
	CString strCommand;
	strCommand.Format (_T("remove %s --version %s"), * pstrPackage, m_ctrlPackageTree.GetItemText (hTreeItem));
	if (! EvalTclFile (3, strCommand)) // if not successful
		return false;
	
	m_ctrlPackageTree.DeleteItem (hTreeItem); // remove the selected item from the tree
	return true;
}

void CPkgAdminDlg::OnPkgadminRepository() 
{
	CFileDialog dlg (TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, _T("eCos Package Database Files (ecos.db)|ecos.db||"), GetParent ());
	dlg.m_ofn.lpstrTitle = _T("Open eCos Package Database File");

	int nStatus;
	do
	{
		nStatus = dlg.DoModal ();
		if (IDOK == nStatus)
		{
			const CString strPathName = dlg.GetPathName ();
			const int nPathNameIndex = strPathName.ReverseFind (_TCHAR('\\'));
			ASSERT (nPathNameIndex != -1);
			m_strRepository = strPathName.Mid (0, nPathNameIndex);
			m_strRepository.Replace (_TCHAR('\\'), _TCHAR('/'));
		}
	}
	while ((IDOK == nStatus) && ! PopulatePackageTree (m_strRepository));
}

void CPkgAdminDlg::OnSelchangedPkgadminTree(NMHDR*, LRESULT* pResult) 
{
	//NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

	// enable the remove button only if a node is selected
	m_btnRemove.EnableWindow (NULL != m_ctrlPackageTree.GetSelectedItem ());

	*pResult = 0;
}

std::string CPkgAdminDlg::UnicodeToStdStr(LPCTSTR str)
{
	int nLength = 1 + _tcslen (str);
	char * pszString = new char [nLength];

	#ifdef _UNICODE
	WideCharToMultiByte (CP_ACP, 0, str, -1, pszString, nLength, NULL, NULL);
	#else
	strcpy (pszString, str);
	#endif

	std::string stdstr = std::string (pszString);
	delete [] pszString;
	return stdstr;
}

bool CPkgAdminDlg::FindUserToolsPath()
{
	HKEY hKey;
	if (ERROR_SUCCESS != RegOpenKeyEx (HKEY_CURRENT_USER, _T("Software\\Red Hat\\eCos\\Configuration Tool\\User Tools"), 0, KEY_READ, &hKey))
		return false;
	
	TCHAR szBuffer [MAX_PATH + 1];
	DWORD dwBufferLength = MAX_PATH + 1;
	LONG lStatus = RegQueryValueEx (hKey, _T("Folder"), NULL, NULL, (LPBYTE) szBuffer, &dwBufferLength);
	RegCloseKey (hKey);
	if (ERROR_SUCCESS != lStatus)
		return false;

	m_strUserTools = szBuffer;
	TRACE (_T("User tools found at %s\n"), m_strUserTools);
	return ! m_strUserTools.IsEmpty ();
}
