/************************************************************************/
/* PluginBoss plugin manager                                            */
/*																		*/
/* By Steve Pedler, March 2012		     								*/
/************************************************************************/

// pib.cpp

/*
Copyright (C) 2012  Steve Pedler

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 3 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, see <http://www.gnu.org/licenses/>.
*/

// include files
#include "c4d.h"
#include "pib.h"
#include "c4d_symbols.h"
#include "customgui_base.h"


/************************************************************************/
/* PIBDialog class functions                                            */
/************************************************************************/

//************************************
// Method:    PIBDialog
// FullName:  PIBDialog::PIBDialog
// Access:    public 
// Returns:   
// Qualifier:
// Parameter: void
// Function:  class constructor
//************************************
PIBDialog::PIBDialog(void)
{
}

//************************************
// Method:    CreateLayout
// FullName:  PIBDialog::CreateLayout
// Access:    virtual public 
// Returns:   Bool
// Qualifier:
// Parameter: void
// Function:  create the initial dialog layout
//************************************
Bool PIBDialog::CreateLayout(void)
{
	Bool result = TRUE;
	BaseContainer lvLayout;
	BaseContainer bcBase;

	// call the base class function first
	GeDialog::CreateLayout();

	// create a resource object and initialise it to the plugin's path
	GeResource dlg_res;
	dlg_res.Init(GeGetPluginPath());
	result = LoadDialogResource(IDD_PIB_DIALOG, &dlg_res, 0);

	// if we couldn't load the resource, print an error messge to the console
	if(!result)
		GePrint("Plugin Boss: could not load the resource file.");

	// attach the two list views
	lvActive.AttachListView(this, IDC_PIB_ACTIVEPLUGS);
	lvDeact.AttachListView(this, IDC_PIB_DEACTPLUGS);
	lvLayout.SetLong('name', LV_COLUMN_TEXT);
	lvActive.SetLayout(1, lvLayout);
	lvDeact.SetLayout(1, lvLayout);
	lvActive.SetProperty(SLV_MULTIPLESELECTION, 1);
	lvDeact.SetProperty(SLV_MULTIPLESELECTION, 1);

	// attach user area
	AttachUserArea(bannerUA, IDC_BANNER, USERAREA_0);

	// add menus
	MenuFlushAll();
	MenuSubBegin(GeLoadString(IDS_PIB_MENU_FILE));
	MenuAddString(PIBMENU_FILE_COMMIT, GeLoadString(IDS_PIB_MENU_FILE_COMMIT));
	MenuAddString(PIBMENU_FILE_RESET, GeLoadString(IDS_PIB_MENU_FILE_RESET));
	MenuAddSeparator();
	MenuAddString(PIBMENU_FILE_RESTART, GeLoadString(IDS_PIB_MENU_FILE_RESTART));
	MenuAddSeparator();
	MenuAddString(PIBMENU_FILE_EXIT, GeLoadString(IDS_PIB_MENU_FILE_EXIT));
	MenuSubEnd();
	MenuSubBegin(GeLoadString(IDS_PIB_MENU_EDIT));
	MenuAddString(PIBMENU_EDIT_ACT, GeLoadString(IDS_PIB_MENU_EDIT_ACT));
	MenuAddString(PIBMENU_EDIT_DEACT, GeLoadString(IDS_PIB_MENU_EDIT_DEACT));
	MenuAddSeparator();
	MenuAddString(PIBMENU_EDIT_ACTALL, GeLoadString(IDS_PIB_MENU_EDIT_ACTALL));
	MenuAddString(PIBMENU_EDIT_DEACTALL, GeLoadString(IDS_PIB_MENU_EDIT_DEACTALL));
	MenuSubEnd();
	MenuSubBegin(GeLoadString(IDS_PIB_MENU_HELP));
	MenuAddString(PIBLMENU_HELP_HELP, GeLoadString(IDS_PIB_MENU_HELP_SHOW));
	MenuAddString(PIBMENU_HELP_ABOUT, GeLoadString(IDS_PIB_MENU_HELP_ABOUT));
	MenuSubEnd();

	// set dialog title
	SetTitle(GeLoadString(IDS_PIB_DIALOGTITLE));

	// return status result to C4D
	return result;
}

//************************************
// Method:    InitValues
// FullName:  PIBDialog::InitValues
// Access:    virtual public 
// Returns:   Bool
// Qualifier:
// Parameter: void
// Function:  set up the initial values in the dialog box
//************************************
Bool PIBDialog::InitValues(void)
{
	// first call the base class function and return FALSE if there was an error
	if(!GeDialog::InitValues())
		return FALSE;

	// get the plugin paths
	GetPluginPaths();

	// fill the lists of active and inactive plugins
	RebuildLists();

	return TRUE;
}

//************************************
// Method:    Command
// FullName:  PIBDialog::Command
// Access:    virtual public 
// Returns:   Bool
// Qualifier:
// Parameter: LONG id
// Parameter: const BaseContainer & msg
// Function:  respond to button clicks and menu commands
//************************************
Bool PIBDialog::Command(LONG id,const BaseContainer &msg)
{
	BaseContainer bc;
	Filename fn;

	switch(id)
	{
	// buttons (and any identical menu commands)
	case IDC_PIB_BUTTONRESTART:
	case PIBMENU_FILE_RESTART:
		if(QuestionDialog(IDS_PIB_QNRESTART))
			RestartApplication();
		break;

	case IDC_PIB_BUTTONCANCEL:
	case PIBMENU_FILE_EXIT:
		Close();
		break;

	case IDC_PIB_BUTTONACT:
	case PIBMENU_EDIT_ACT:
		MoveToActive();
		break;

	case IDC_PIB_BUTTONDEACT:
	case PIBMENU_EDIT_DEACT:
		MoveToInactive();
		break;

	case IDC_PIB_BUTTONCOMMIT:
	case PIBMENU_FILE_COMMIT:
		if(QuestionDialog(IDS_PIB_QN_COMMIT))
			CommitChanges();
		break;

	// other menus
	case PIBMENU_FILE_RESET:
		// reset the entire plugin list to reflect what is on disk
		RebuildLists();
		break;

	case PIBMENU_EDIT_ACTALL:
		ActivateAll();
		break;

	case PIBMENU_EDIT_DEACTALL:
		DeactivateAll();
		break;

	case PIBLMENU_HELP_HELP:
		fn = GeGetPluginPath()  + Filename("PluginBoss.pdf");
		GeExecuteFile(fn);
		break;

	case PIBMENU_HELP_ABOUT:
		ShowAbout();
		break;
	}

	return TRUE;
}

//************************************
// Method:    AskClose
// FullName:  PIBDialog::AskClose
// Access:    virtual public 
// Returns:   Bool
// Qualifier:
// Parameter: void
// Function:  do anything reqiured before the dialog closes
//************************************
Bool PIBDialog::AskClose(void)
{
	return FALSE;
}

//////////////////////////////////////////////////////////////////////////
// utility functions

//************************************
// Method:    GetPluginPaths
// FullName:  PIBDialog::GetPluginPaths
// Access:    public 
// Returns:   Bool
// Qualifier:
// Parameter: void
// Function:  retrieves the two paths where plugins could be located
//************************************
Bool PIBDialog::GetPluginPaths(void)
{
	appPluginpath = GeGetStartupPath() + Filename("plugins");
	prefPluginPath = GeGetStartupWritePath() + Filename("plugins");

	return TRUE;
}

//************************************
// Method:    RecursivePluginFinder
// FullName:  PIBDialog::RecursivePluginFinder
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: LONG pluginFolder
// Function:  searches the plugin folder recursively and stores all the folders with plugins in them into dynamic arrays
//************************************
void PIBDialog::RecursivePluginFinder(Filename pluginFolder)
{
	AutoAlloc<BrowseFiles> bf;
	Filename fn;

	// first check for subdirs
	bf->Init(pluginFolder, 0);
	if(bf)
	{
		while(bf->GetNext())
		{
			if(bf->IsDir() && bf->GetFilename().GetFileString() != "res" && bf->GetFilename().GetFileString() != "PluginBoss")			// don't include PluginBoss and no need to search the resource folder!
			{
				RecursivePluginFinder(pluginFolder + bf->GetFilename());
			}
		}
	}
	
	// now check for plugin files
	bf->Init(pluginFolder, 0);
	if(bf)
	{
		while(bf->GetNext())
		{
			if(!(bf->IsDir()))
			{
				fn = bf->GetFilename();
				// check for any currently active plugins
				if(fn.CheckSuffix("cof") || fn.CheckSuffix("cob") || fn.CheckSuffix("cdl") || fn.CheckSuffix("cdl64") || fn.CheckSuffix("pyp") || fn.CheckSuffix("pype") || fn.CheckSuffix("pypv") || fn.CheckSuffix("dylib"))
				{
					livePlugs[livePlugCount++] = pluginFolder;
					break;
				}
				// now check for any inactive plugins
				if(fn.CheckSuffix("cof_DEACT") || fn.CheckSuffix("cob_DEACT") || fn.CheckSuffix("cdl_DEACT") || fn.CheckSuffix("cdl64_DEACT") || fn.CheckSuffix("pyp_DEACT") || fn.CheckSuffix("pype_DEACT") || fn.CheckSuffix("pypv_DEACT") || fn.CheckSuffix("dylib_DEACT"))
				{
					deadPlugs[deadPlugCount++] = pluginFolder;
					break;
				}
			}
		}
	}
}

//************************************
// Method:    ClearPluginLists
// FullName:  PIBDialog::ClearPluginLists
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: void
// Function:  clear the two plugin lists in the dialog
//************************************
void PIBDialog::ClearPluginLists(void)
{
	LONG i, id;
	BaseContainer bc;

	for(i = lvActive.GetItemCount() - 1; i >= 0; i--)
	{
		lvActive.GetItemLine(i, &id, &bc);
		lvActive.RemoveItem(id);
	}
	for(i = lvDeact.GetItemCount() - 1; i >= 0; i--)
	{
		lvDeact.GetItemLine(i, &id, &bc);
		lvDeact.RemoveItem(id);
	}
	lvActive.DataChanged();
	lvDeact.DataChanged();
	EventAdd(EVENT_0);

}

//************************************
// Method:    MoveToInactive
// FullName:  PIBDialog::MoveToInactive
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: void
// Function:  moves selected plugins from the active list to the inactive list
//************************************
void PIBDialog::MoveToInactive(void)
{
	AutoAlloc<BaseSelect>bs;
	LONG i;

	lvActive.GetSelection(bs);					// get the selected items in the active plugins list
	for(i = 0; i < livePlugs.GetCount(); i++)
	{
		if(bs->IsSelected(i))					// for each entry, if it's selected, move it to the inactive list
		{
			deadPlugs.Push(livePlugs[i]);
			deadPlugCount++;
		}
	}
	// now remove the same plugins from the active list
	for(i = livePlugs.GetCount() - 1; i >= 0; i--)
	{
		if(bs->IsSelected(i))
		{
			livePlugs.Remove(i);
			livePlugCount--;
		}
	}
	FillLists();
}

//************************************
// Method:    MoveToActive
// FullName:  PIBDialog::MoveToInactive
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: void
// Function:  moves selected plugins from the inactive list to the active list
//************************************
void PIBDialog::MoveToActive(void)
{
	AutoAlloc<BaseSelect>bs;
	LONG i;

	lvDeact.GetSelection(bs);					// get the selected items in the inactive plugins list
	for(i = 0; i < deadPlugs.GetCount(); i++)
	{
		if(bs->IsSelected(i))					// for each entry, if it's selected, move it to the active list
		{
			livePlugs.Push(deadPlugs[i]);
			livePlugCount++;
		}
	}
	// now remove the same plugins from the inactive list
	for(i = deadPlugs.GetCount() - 1; i >= 0; i--)
	{
		if(bs->IsSelected(i))
		{
			deadPlugs.Remove(i);
			deadPlugCount--;
		}
	}
	FillLists();
}

//************************************
// Method:    DeactivateAll
// FullName:  PIBDialog::DeactivateAll
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: void
// Function:  moves all plugins to the dead list
//************************************
void PIBDialog::DeactivateAll(void)
{
	LONG i;

	for(i = 0; i < livePlugs.GetCount(); i++)
	{
		deadPlugs.Push(livePlugs[i]);
		deadPlugCount++;
	}
	livePlugs.FreeArray();
	livePlugCount = 0;
	FillLists();
}

//************************************
// Method:    ActivateAll
// FullName:  PIBDialog::ActivateAll
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: void
// Function:  moves all plugins to the live list
//************************************
void PIBDialog::ActivateAll(void)
{
	LONG i;

	for(i = 0; i < deadPlugs.GetCount(); i++)
	{
		livePlugs.Push(deadPlugs[i]);
		livePlugCount++;
	}
	deadPlugs.FreeArray();
	deadPlugCount = 0;
	FillLists();
}

//************************************
// Method:    FillLists
// FullName:  PIBDialog::FillLists
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: void
// Function:  populate the two list boxes from the dynamic arrays holding the list of plugins
//************************************
void PIBDialog::FillLists(void)
{
	LONG i;
	BaseContainer bc;

	ClearPluginLists();
	for(i = 0; i < livePlugs.GetCount(); i++)
	{
		bc.SetString('name', livePlugs[i].GetFileString());
		lvActive.SetItem(i, bc);
	}
	for(i = 0; i < deadPlugs.GetCount(); i++)
	{
		bc.SetString('name', deadPlugs[i].GetFileString());
		lvDeact.SetItem(i, bc);
	}
	lvActive.DataChanged();
	lvDeact.DataChanged();
	EventAdd(EVENT_0);
}

//************************************
// Method:    CommitChanges
// FullName:  PIBDialog::CommitChanges
// Access:    public 
// Returns:   Bool
// Qualifier:
// Parameter: void
// Function:  renames plugins using the lists as source
//************************************
Bool PIBDialog::CommitChanges(void)
{
	AutoAlloc<BrowseFiles> bf;
	Filename pluginFolder, fn, fnold, fnew;
	LONG i, j, pos;
	String dir, ext;
	BaseContainer bc;

	// check each folder in each list for the presence of a live plugin file and change the extension to deactivate it
	for(i = 0; i < deadPlugs.GetCount(); i++)
	{
		pluginFolder = deadPlugs[i];
		bf->Init(pluginFolder, 0);
		if(bf)
		{
			while(bf->GetNext())
			{
				if(!(bf->IsDir()))
				{
					fn = bf->GetFilename();
					if(fn.CheckSuffix("cof") || fn.CheckSuffix("cob") || fn.CheckSuffix("cdl") || fn.CheckSuffix("cdl64") || fn.CheckSuffix("pyp") || fn.CheckSuffix("pype") || fn.CheckSuffix("pypv") || fn.CheckSuffix("dylib"))
					{
						// live plug found, so deactivate it
						dir = fn.GetFileString();							// get the extension so we can add the string '_DEACT' to it
						for(j = dir.GetLength(); j >= 0; j--)
						{
							if(dir[j] == '.')
								break;
						}
						ext = dir.SubStr(j + 1, dir.GetLength() - j - 1);
						ext = ext + "_DEACT";
						fnold = pluginFolder + fn;
						fnew = fnold;
						fnew.ClearSuffixComplete();
						fnew.SetSuffix(ext);
						if(!fnold.GetString().FindFirst("PluginBoss", &pos))							// not allowed to deactivate Plugin Boss itself
							GeFRename(fnold, fnew);
					}
				}
			}
		}
	}
	// now repeat for inactive plugins to be activated
	for(i = 0; i < livePlugs.GetCount(); i++)
	{
		pluginFolder = livePlugs[i];
		bf->Init(pluginFolder, 0);
		if(bf)
		{
			while(bf->GetNext())
			{
				if(!(bf->IsDir()))
				{
					fn = bf->GetFilename();
					if(fn.CheckSuffix("cof_DEACT") || fn.CheckSuffix("cob_DEACT") || fn.CheckSuffix("cdl_DEACT") || fn.CheckSuffix("cdl64_DEACT") || fn.CheckSuffix("pyp_DEACT") || fn.CheckSuffix("pype_DEACT") || fn.CheckSuffix("pypv_DEACT") || fn.CheckSuffix("dylib_DEACT"))
					{
						// dead plug found, so activate it
						dir = fn.GetFileString();						// get the extension so we can remove the string '_DEACT' from it
						for(j = dir.GetLength(); j >= 0; j--)
						{
							if(dir[j] == '.')
								break;
						}
						ext = dir.SubStr(j + 1, dir.GetLength() - j - 1);
						ext = ext.SubStr(0, ext.GetLength() - 6);
						fnold = pluginFolder + fn;
						fnew = fnold;
						fnew.ClearSuffixComplete();
						fnew.SetSuffix(ext);
						if(!fnold.GetString().FindFirst("PluginBoss", &pos))							// can't activate Plugin Boss itself
							GeFRename(fnold, fnew);
					}
				}
			}
		}
	}

	// finally, rebuild the lists
	RebuildLists();

	return TRUE;
}

//************************************
// Method:    ShowAbout
// FullName:  PIBDialog::ShowAbout
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: void
// Function:  show the plugin version and copyright
//************************************
void PIBDialog::ShowAbout(void)
{
	String about;

	about = GeLoadString(IDS_PIB_NAME);
	about += "\n\n";
	about += GeLoadString(IDS_PIB_VERSION);
	about += ", ";
	about += GeLoadString(IDS_PIB_DATE);
	about += "\n\n";
	about += GeLoadString(IDS_PIB_COPYRIGHT);
	about += "\n";
	about += GeLoadString(IDS_PIB_GPL);

	MessageDialog(about);
}

//************************************
// Method:    RebuildLists
// FullName:  PIBDialog::RebuildLists
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: void
// Function:  completely rebuilds the plugin lists from scratch
//************************************
void PIBDialog::RebuildLists(void)
{
	livePlugCount = 0;
	deadPlugCount = 0;
	livePlugs.FreeArray();
	deadPlugs.FreeArray();
	GetPluginPaths();
	RecursivePluginFinder(appPluginpath);
	RecursivePluginFinder(prefPluginPath);
	FillLists();
}

/************************************************************************/
/* Banner UserArea functions                                    */
/************************************************************************/

//************************************
// Method:    Banner
// FullName:  Banner::Banner
// Access:    public 
// Returns:   
// Qualifier:
// Function:  class constructor
//************************************
Banner::Banner()
{
	// allocate bitmap
	bmp = NULL;
	bmp = BaseBitmap::Alloc();
}

//************************************
// Method:    ~Banner
// FullName:  Banner::~Banner
// Access:    virtual public 
// Returns:   
// Qualifier:
// Function:  class destructor
//************************************
Banner::~Banner()
{
	// free the bitmap
	BaseBitmap::Free(bmp);
}

//************************************
// Method:    Init
// FullName:  Banner::Init
// Access:    virtual private 
// Returns:   Bool
// Qualifier:
// Function:  initialise the user area
//************************************
Bool Banner::Init()
{
	Filename fname;
	IMAGERESULT result;

	fname = GeGetPluginPath() + Filename("res");
	bmpToLoad.SetDirectory(fname);
	bmpToLoad.SetFile("banner.tif");
	if(bmp)
	{
		result = bmp->Init(bmpToLoad);
		if(result != IMAGERESULT_OK)
		{
			GePrint("Plugin Boss: Error " + LongToString(result) + " in loading bitmap " + bmpToLoad.GetString());
		}
	}

	return TRUE;
}

//************************************
// Method:    DrawMsg
// FullName:  Banner::DrawMsg
// Access:    virtual private 
// Returns:   void
// Qualifier:
// Parameter: LONG x1
// Parameter: LONG y1
// Parameter: LONG x2
// Parameter: LONG y2
// Parameter: const BaseContainer & msg
// Function:  draw the banner bitmap in the dialog box
//************************************
void Banner::DrawMsg(LONG x1, LONG y1, LONG x2, LONG y2, const BaseContainer &msg)
{
	// draw the bitmap
	if(bmp)
	{
		LONG w = GetWidth();
		LONG h = GetHeight();
		LONG xStart = (bmp->GetBw() - w)/2;
		if (xStart < 0)
			xStart = 0;
		LONG yStart = (bmp->GetBh() - h)/2;
		if (yStart < 0)
			yStart = 0;
		DrawBitmap(bmp, 0, 0, w, h, 0, 0, bmp->GetBw(), bmp->GetBh(), BMP_NORMALSCALED);
	}
}

// ------------------- end of Banner class functions-------------------------


/************************************************************************/
/* Plugin Boss CommandData functions                                    */
/************************************************************************/


//************************************
// Method:    Execute
// FullName:  PluginBoss::Execute
// Access:    virtual public 
// Returns:   Bool
// Qualifier:
// Parameter: BaseDocument * doc
// Function:  this function is caled whenever the user clicks the name of the plugin in the Plugins menu
//************************************
Bool PluginBoss::Execute(BaseDocument *doc)
{
	// all we do here is open the dialog non-modally

	return dlg.Open(DLG_TYPE_ASYNC, ID_PLUGINBOSS, -1, -1, 500, 500);
}

//************************************
// Method:    GetState
// FullName:  PluginBoss::GetState
// Access:    virtual public 
// Returns:   LONG
// Qualifier:
// Parameter: BaseDocument * doc
// Function:  return how the plugin name is to be displayed in the Plugins menu
//************************************
LONG PluginBoss::GetState(BaseDocument *doc)
{
	return CMD_ENABLED;
}


/************************************************************************/
/* Other functions                                                      */
/************************************************************************/

//************************************
// Method:    RegisterPluginBoss
// FullName:  RegisterPluginBoss
// Access:    public 
// Returns:   Bool
// Qualifier:
// Parameter: void
// Function:  register the plugin with C4D
//************************************
Bool RegisterPluginBoss(void)
{
	return RegisterCommandPlugin(ID_PLUGINBOSS, GeLoadString(IDS_PIB_NAME), 0, AutoBitmap("pib2.tif"), GeLoadString(IDS_PIB_HELPSTRING), gNew PluginBoss);
}
