/********************************
imagedialog.cpp
Mike MacIntyre, David Boys
Function definitions for Class ImageDialog
last updated : August 9, 2000
*********************************/

#include "imagedialog.h"
#include "wx-view.h"

#include "filecanvas.h"
#include "imagepanel.h"
#include "PreviewPanel.h"


BEGIN_EVENT_TABLE(ImageDialog, wxDialog)
    //EVT_TEXT_ENTER(ID_enter_box, ImageDialog::OnEnter)
    //EVT_CHOICE(ID_filter,        ImageDialog::OnChoice)
    EVT_TIMER(ID_timer, ImageDialog::OnTimer)
END_EVENT_TABLE()


///// FUNCTION DEFINITIONS /////

/**********************************************************************
   Function: ImageDialog::ImageDialog
   Receives: wxWindow*, wxWindowID id, const wxString&, const wxPoint&
             const wxSize&, const wxString&, const wxString&, int
   Returns: N/A  (constructor)
   Description:
            Configures dialog box designed for image search
            and selection.
**********************************************************************/
ImageDialog::ImageDialog(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos,
             const wxSize& size, const wxString & map_filename  , const wxString & config_dir, int icon_size)
    : wxDialog(parent, id, title, pos, size),
      config_dir(config_dir), map_filename(map_filename),
      tree(NULL), panel_timer(this, ID_timer)
{
// constructor

	int w, h;
	GetClientSize(&w, &h);

	wxString cancel = translation["Cancel"];
	wxString ok = translation["OK"];
	wxString imageprev = translation["Image Preview"];
	wxString filter_lbl = translation["Filter:"];
	wxString file = translation["File:"];							
	
	ok_button = new wxButton(this, wxID_OK, ok);
	cancel_button = new wxButton(this, wxID_CANCEL, cancel);
	
	int but_w, but_h;
	ok_button -> GetSize(&but_w, &but_h);
	
	ok_button -> Move(w - (but_w + BORDER), h - (2 * (but_h + BORDER)));
	cancel_button -> Move (w - (but_w + BORDER), h - (but_h + BORDER));
	
	preview_label = new wxStaticText(this, -1, "",
									 wxPoint(0, 0),
									 wxSize(0, 0), wxALIGN_LEFT);	
	
	preview_label->SetLabel(imageprev);
	int prev_lbl_w, prev_lbl_h;
	preview_label->GetSize(&prev_lbl_w, &prev_lbl_h);

	const int buttons_and_spaces = (4 * BORDER) + (but_h * 2);
	const int tree_width = static_cast <int> ( TREE_WIDTH_RATIO * (w - TREE_CANVAS_SPACES));
	const int preview_info_height = (but_h * 2) + BORDER;
	const int preview_info_width = tree_width;
	const int preview_width = tree_width;
	const int tree_height = static_cast <int> (h - TREE_PREVIEW_SPACES - preview_info_height -
											   PREVIEW_LABEL_HEIGHT - (PREVIEW_HEIGHT_RATIO * preview_width)
											   + ROUNDING);		
	const int preview_height = static_cast <int> (PREVIEW_HEIGHT_RATIO * preview_width + ROUNDING);
	const int scroll_file_width =  static_cast <int> (FILE_CANVAS_WIDTH_RATIO * (w - TREE_CANVAS_SPACES) +
													  ROUNDING);
	const int scroll_file_height =  h - buttons_and_spaces;
	const int text_ctrls_height = but_h;
	
	config_icon_size = icon_size;		
	
	preview = new PreviewPanel(this, wxPoint(BORDER, BORDER + tree_height + PREVIEW_LABEL_HEIGHT),
							         wxSize(preview_width, preview_height));
	
	preview_label->Move(BORDER, BORDER + tree_height + BORDER);
							
	preview_info = new wxPanel(this, -1,
							   wxPoint(BORDER, h - BORDER - preview_info_height),
	                           wxSize(preview_info_width, preview_info_height));
	
	preview_info_label = new wxStaticText(preview_info, - 1, "",
									      wxPoint(0, 0),
                                          wxSize(0, 0), wxALIGN_LEFT);

	preview_info_header = new wxStaticText(preview_info, -1, "",
										   wxPoint(0, 0),
										   wxSize(0, 0), wxALIGN_LEFT);

	scroll_page = new FileCanvas(this, ID_img_canvas,
								 wxPoint(tree_width + (2 * BORDER) , BORDER),
   	                             wxSize(scroll_file_width, scroll_file_height), config_icon_size,
   	                             temp_selections, preview);
   	
	filter_label = new wxStaticText(this, -1, "",
									wxPoint(0, 0),
									wxSize(0, 0), wxALIGN_LEFT);
																			
	file_label = new wxStaticText(this, -1, "",
								  wxPoint(0, 0),
								  wxSize(0, 0), wxALIGN_LEFT);

	filter_label -> SetLabel(filter_lbl);
	file_label -> SetLabel(file);							
		  								
	const int label_width = max_length_of_labels();
	const int label_height = max_height_of_labels();
	
	const int choice_x = ((w - but_w - (4 * BORDER) - TEXT_CTRLS_WIDTH));
	const int choice_y = h - (but_h + BORDER);
	
	init_filespec_choices();
	
	enter_box = new wxTextCtrl(this, ID_enter_box, "",
							   wxPoint(choice_x, choice_y - (but_h + BORDER)),
							   wxSize(TEXT_CTRLS_WIDTH, text_ctrls_height), wxTE_PROCESS_ENTER);
							
    filter = new wxChoice(this, ID_filter,
						  wxPoint(choice_x, choice_y ),
						  wxSize(TEXT_CTRLS_WIDTH, text_ctrls_height), CHOICE_SIZE, filespec_choices);							
	
	filter_label -> Move(choice_x - label_width - BORDER, choice_y + ((but_h - label_height) / 2));
	file_label -> Move(choice_x - label_width - BORDER, choice_y  - (but_h + BORDER) + ((but_h - label_height) / 2));
	
	preview_info_label ->Show (false);
	preview_info_header-> Show(false);

    enter_box->Show (false);
    filter->Show(false);
    filter_label->Show(false);
    file_label->Show(false);

    read_map_from_file();
}



/**********************************************************************
   Function: ImageDialog::~ImageDialog
   Receives: N/A (destructor)
   Returns:  N/A (destructor)
   Description:
            Deallocates memory. 
**********************************************************************/

ImageDialog::~ImageDialog()
{
    delete (enter_box);
    delete (preview_label);
    delete (filter_label);
    delete (file_label); 
    delete (ok_button);
    delete (cancel_button); 
    delete (preview);
    delete (filter);
    delete (scroll_page);
    delete (preview_info);
}

/**********************************************************************
   Function: ImageDialog::get_full_path
   Receives: const wxTreeItemId&
   Returns:  wxString
   Description:
              returns the full path  from the rootID of the
              TreeCtrl to the ID received, separated by
              forward slashes.               
**********************************************************************/
wxString ImageDialog::get_full_path(const wxTreeItemId  & id)
{

    wxString result = tree->GetItemText(id);
    wxTreeItemId root_id = tree -> GetRootItem();
	
	if(root_id != id)
	{
        wxTreeItemId temp_id = tree -> GetParent(id);
	
		while( temp_id != root_id)
		{
            result = tree->GetItemText(temp_id) + "/" + result;                                
            temp_id = tree -> GetParent(temp_id);
		}

        result = tree->GetItemText(root_id) + "/" +  result;
	}
	return result;	
}



/**********************************************************************
   Function: ImageDialog::parse_textctrl
   Receives: wxString&, wxString&
   Returns:  N/A (void)
   Description:
               Divides the entry in the textcontrol into
               path and filespec.
**********************************************************************/

void ImageDialog::parse_textctrl(wxString & path, wxString & filespec)
{
    
    wxString search_spec = enter_box->GetValue();

    if(wxIsAbsolutePath(search_spec))
	{
        path = wxPathOnly(search_spec); 
        filespec = wxFileNameFromPath(search_spec);
	}	
	else
	{
        wxTreeItemId id = tree->GetSelection();
        path = get_full_path(id);
        filespec = search_spec;
	}
}

/**********************************************************************
   Function: ImageDialog::enable_filespec_controls 
   Receives: N/A
   Returns:  (void)
   Description:
              Allows subclasses the option to use the controls
              if desired.
**********************************************************************/
void ImageDialog::enable_filespec_controls()
{
   filter->Show(true);
   enter_box->Show(true);
   filter_label->Show(true);
   file_label->Show(true);
}

/**********************************************************************
   Function: ImageDialog::get_filespec
   Receives: ()
   Returns:  wxString
   Description:
              Returns the filespec associated with the wxChoice
              item currently selected.
**********************************************************************/
wxString ImageDialog::get_filespec () const
{
     return filespec_filters[filter->GetSelection()];
}

/**********************************************************************
   Function: ImageDialog::init_filespec_choices
   Receives: ()
   Returns:  (void)
   Description:
             Sets up the association between the  filters used
             internally, and the choices offered to the end user.
**********************************************************************/
void ImageDialog::init_filespec_choices()
{
	wxString choices[CHOICE_SIZE] = {translation["All Image Files"], "PNG (*.png)", "JPEG (*.jpg/ *.jpeg)", "TIFF (*.tif/ *.tiff)",
                                    "GIF (*.gif)", "PCX (*.pcx)", "PNM (*.pnm)", "Bitmap (*.bmp)"};

    wxString filters[CHOICE_SIZE] = {"*.*", "*.png", "*.jp*g", "*.ti*f",
                                    "*.gif", "*.pcx", "*.pnm", "*.bmp"};

    copy (choices, choices + CHOICE_SIZE, filespec_choices);
    copy (filters, filters + CHOICE_SIZE, filespec_filters);
}
/**********************************************************************
   Function: ImageDialog::set_tree_size_and_position
   Receives: ()
   Returns:  (void)
   Description:
             Places subclass defined tree in its alloted space
**********************************************************************/

void ImageDialog::set_tree_size_and_position()
{	
	int w, h, but_w, but_h;
	GetClientSize(&w, &h);
	ok_button -> GetSize(&but_w, &but_h);
	int preview_info_height = (2 * but_h) + (BORDER);
	
	tree->SetSize(BORDER, BORDER,
	              static_cast <int> ( TREE_WIDTH_RATIO * (w - TREE_CANVAS_SPACES) + ROUNDING),
		          static_cast <int> (h - TREE_PREVIEW_SPACES - preview_info_height - PREVIEW_LABEL_HEIGHT -
		          					(PREVIEW_HEIGHT_RATIO * ( TREE_WIDTH_RATIO * (w - TREE_CANVAS_SPACES))) + ROUNDING ));		
}	


/**********************************************************************
   Function: ImageDialog::ShowModal
   Receives: ()
   Returns:  wxID_OK or wxID_CANCEL
   Description:
              fill image path vector if OK button selected 
**********************************************************************/
int ImageDialog::ShowModal()
{
    int result = wxDialog::ShowModal();     // base class does the job
    wxEndBusyCursor();

    if (result == wxID_OK)
    {
        selected_files.swap(temp_selections);
    }

    return result;
}

/**********************************************************************
   Function: ImageDialog::iconized_image_exists
   Receives: (const wxString& full_path)
   Returns:  bool
   Description:
             Checks for existense of a map item pairing
             the full_path received with that of an iconized
             image. Returns true if match found.
**********************************************************************/

bool ImageDialog::iconized_image_exists(const wxString & full_path) const
{

    map <wxString , wxString  > :: const_iterator i;

    for(i = icon_files.begin(); i != icon_files.end(); ++i)
    {
        if(i->first == full_path)
        {
            return true;
        }
    }
    return false;
}

/**********************************************************************
   Function: ImageDialog::get_icon
   Receives: (wxImage* image, const wxString& full_path)
   Returns:  bool
   Description:
              Attempts to load iconized image. Returns true if successful
              and if iconized image is same as size configured by end user.
**********************************************************************/

bool ImageDialog::get_icon(wxImage * image, const wxString & full_path)
{
    if(!iconized_image_exists(full_path))
    {
        return false;
    }

    wxLogNull no_message_if_load_fails;
    {
        if( !image->LoadFile (icon_files[full_path], wxBITMAP_TYPE_ANY ))
            return false;
    }

    if (max (image -> GetWidth(), image->GetHeight()) != config_icon_size )   		
    {
        wxRemoveFile(icon_files[full_path]);   		
   		return false;
   	}	
   	
   	return true;
}


/**********************************************************************
   Function: ImageDialog::get_original_image
   Receives: (wxImage* image , wxString& full_path , bool & map_changed)
   Returns:  bool
   Description:
              Loads an image and creates an icon for it. (adjusting map)
              Returns true if successful.
**********************************************************************/	
bool ImageDialog::get_original_image(wxImage * image, const wxString & full_path, bool & map_changed)
{
    wxLogNull no_message_if_load_fails;

    if (image->LoadFile(full_path, wxBITMAP_TYPE_ANY))
    {
       icon_files[full_path] = config_dir + "/cache/" + generate_filename(full_path);
       iconize_image(image, config_icon_size);
       wxBitmap * bm = new wxBitmap(image->ConvertToBitmap());
       bm->SaveFile(icon_files[full_path], wxBITMAP_TYPE_BMP);
       map_changed = true;
       delete (bm);
       return true;
    }

    return false;
}

/**********************************************************************
   Function: ImageDialog::generate_filename
   Receives: (const wxString& full_path)
   Returns:  wxString
   Description:
           Generates a unique  filename based on that received.
**********************************************************************/	
wxString ImageDialog::generate_filename(const wxString & full_path) const
{
	wxString result = wxFileNameFromPath(full_path).BeforeLast('.');
	wxString prefix = "";
	
	do
	{
		prefix += (rand() % 26 + 'a');
	}
	while (wxFileExists(config_dir + "/cache/" + prefix + "_" + result + ".bmp"));
	
	result = prefix + "_" + result + ".bmp";
	return result;
}

/**********************************************************************
   Function: ImageDialog::iconize_image
   Receives: (wxImage* image, int max_size)
   Returns:  (void)
   Description:
              Rescales image to a maximumwidth and height
              while maintaining aspect ratio.
**********************************************************************/
void ImageDialog::iconize_image( wxImage * image, int max_size) const
{


    int w = image->GetWidth(), h = image->GetHeight();
    double ratio = min(static_cast<double>(max_size) / h,
                       static_cast<double>(max_size) / w);

    if(ratio < 1)
    {
        image->Rescale((w * ratio) + .5 , (h * ratio) + .5);
    }
}

/**********************************************************************
   Function: ImageDialog::clear_preview
   Receives: ()
   Returns:  (void)
   Description: call public function of class Preview to remove
                preview image from its panel.
**********************************************************************/
void ImageDialog::clear_preview()
{
   	preview -> clear_preview();
    hide_preview_info();
}
/**********************************************************************
   Function: ImageDialog::read_map_from_file
   Receives: ()
   Returns:  (void)
   Description:
              Reads into the original file/icon file  map from a
              configured file.
**********************************************************************/
void ImageDialog::read_map_from_file()
{    
    ifstream  map_file (config_dir + '/' + map_filename);

    if(map_file)
    {
        string map_item;

        while (getline (map_file, map_item))
        {

            icon_files[static_cast<wxString>(map_item.c_str()).BeforeFirst('\t')] =
                       static_cast<wxString>(map_item.c_str()).AfterFirst('\t');
        }
   }
}

/**********************************************************************
   Function:  ImageDialog:: write_map_to_file
   Receives:  ()
   Returns:   (void)
   Description:
              Rewrites the original image filename / icon filename map
              to its file.
**********************************************************************/
void ImageDialog:: write_map_to_file() const
{
    ofstream map_file(config_dir + '/' + map_filename);

    if(map_file)
    {
         map <wxString , wxString  > :: const_iterator i;// = icon_files.begin();

         for(i = icon_files.begin(); i != icon_files.end(); ++i)
         {
             map_file << i->first + '\t' + i->second << endl;
         }
    }
}

/**********************************************************************
   Function: ImageDialog:: process_ok ()
   Receives: ()
   Returns:  (void)
   Description:
              Closes the dialog box returning code for OK button.
**********************************************************************/
void ImageDialog::process_ok()
{
	EndModal(wxID_OK);
}



/*********************************** EVENT DRIVEN FUNCTIONS ******************************************/

/**********************************************************************
   Function: ImageDialog::reverse_panel_creation_files()
   Receives: ()
   Returns:  (void)
   Description:
              Reverses the order of the vector of file paths that will
              be used to create the icon image panels. Panels  will then
              be created back to front, with the final file path in vector
              being removed (pop_back) at panel creation time.

**********************************************************************/


void ImageDialog::reverse_panel_creation_files()
{
    reverse(panel_creation_files.begin(), panel_creation_files.end());
}

/**********************************************************************
   Function: ImageDialog::begin_panel_creation
   Receives: ()
   Returns:  (void)
   Description:
               Does the housekeeping necessary to begin a new image search
               and display.
**********************************************************************/

void ImageDialog::begin_panel_creation()
{
    panel_timer.Stop();
    panel_creation_files.clear();

    map_changed = false;
    clear_preview();
    clear_panels();

    // now setup timer chain of events to create panels individually

    panel_X = BORDER;
    panel_Y = BORDER;

    int w, h;
    scroll_page -> GetSize(&w, &h);

    const int scroll_bar_Y = wxSystemSettings::GetSystemMetric(wxSYS_HSCROLL_Y);
    panels_per_column = (h - BORDER - scroll_bar_Y ) / (config_icon_size + BORDER);
    max_panel_width = 0;
    panel_counter = 0;

    scroll_page -> set_previous(NULL);
}
/**********************************************************************
   Function: ImageDialog::start_panel_timer()
   Receives: ()
   Returns:  (void)
   Description:
               Allows access to the timer.
**********************************************************************/

void ImageDialog::start_panel_timer()
{
    panel_timer.Start(1, true);
}
/**********************************************************************
   Function: ImageDialog::OnTimer
   Receives: ()
   Returns:  (void)
   Description:
              Triggered by timer event, adds the image corresponding to
              the last file path string to the file canvas, while ensuring
              that the search specifications have not changed in the
              meantime.
**********************************************************************/

void ImageDialog::OnTimer()
{
    wxLogNull no_message;
    vector <wxString> temp = panel_creation_files;
    wxBeginBusyCursor();

    if (!temp.empty())
    {
        wxImage * image = new wxImage();
        int this_file = temp.size() - 1;


        if (this_file >= 0 &&
           (get_icon(image, temp[this_file]) ||
            get_original_image(image, temp[this_file], map_changed)))
        {			        	
             scroll_page->add_panel(temp[this_file] ,  image, panel_X, panel_Y);
             wxYield();
             max_panel_width = max(max_panel_width, scroll_page -> panel_width(panel_counter));
             panel_Y += config_icon_size + BORDER;
             panel_counter ++;

       	     if (panel_counter % panels_per_column == 0)
       	     {
                 panel_Y = BORDER;
                 panel_X += (max_panel_width + WIDTH_SPACES);
                 max_panel_width = 0;
       	     }
       	      	
       	}
       	
       	temp.pop_back();
       	
       	if (temp.empty())
       	{
           	scroll_page -> setscrllbar(panel_X + max_panel_width);

            if (map_changed)
            {
                write_map_to_file();
            }

            if (panel_counter == 0)
            {
            // what do we do here???????
            }
        }
        else
        {
            panel_timer.Start(1, true);
        }
    }


    if (!panel_creation_files.empty())
    {
        panel_creation_files.swap(temp);
    }
    else
    {
        begin_panel_creation();     // reset in case we destroyed the reinitialized variables
                                    // (we were working with temp and resetting variables accordingly)
    }

    wxEndBusyCursor();
}
/**********************************************************************
   Function: ImageDialog::max_length_of_labels
   Receives: ()
   Returns:  integer
   Description:
             Returns the width of the wider of the file label
             and the filter label. Used in formatting the dialog.
**********************************************************************/

       	
int ImageDialog::max_length_of_labels() const
{
	int w1, w2, h1, h2;
	
	file_label -> GetSize(&w1, &h1);
	filter_label -> GetSize(&w2, &h2);
	
	return max(w1, w2);
}

/**********************************************************************
   Function: ImageDialog::max_height_of_labels()
   Receives: ()
   Returns:  integer
   Description:
              Returns the height of the taller of the file label
              and the filter label. Used in formatting the dialog.
**********************************************************************/

int ImageDialog::max_height_of_labels() const
{
	int w1, w2, h1, h2;
	
	file_label -> GetSize(&w1, &h1);
	filter_label -> GetSize(&w2, &h2);
	
	return max(h1, h2);
}

/**********************************************************************
   Function: ImageDialog::show_preview_info
   Receives: (const wxString & path, int h, int w, unsigned long size)
   Returns:  (void)
   Description:
               Formats and sets the label that describes the preview,
               centered beneath the image.
**********************************************************************/


void ImageDialog::show_preview_info(int h, int w, unsigned long size)
{
	int p_h, p_w, pil_w, pil_h, pih_w, pih_h;
    wxString full_path, img_size, file_size;
    preview_info->GetSize(&p_w, &p_h);

    img_size << " " << w << " x " << h << "  " <<  translation["Pixels"] << '\n';
    file_size << " " << size << "  " << translation["Bytes"];

    preview_info_header->SetLabel(translation["Image Info:"]);
    preview_info_header->Move(3, 0);
    preview_info_header->GetSize(&pih_w, &pih_h);
    preview_info_header->Show(true);

	preview_info_label->SetLabel(img_size + file_size);
    preview_info_label->GetSize(&pil_w, &pil_h);
	preview_info_label->Move((p_w - pil_w) / 2, (pih_h));
	preview_info_label->Show(true);	
}

/**********************************************************************
   Function: ImageDialog::hide_preview_info
   Receives: ()
   Returns:  (void)
   Description:
              Removes text from label, and then makes label invisible.
              This function is called internally whenever the actual
              preview image is removed.

**********************************************************************/

void ImageDialog::hide_preview_info()
{
	preview_info_label->SetLabel(wxEmptyString);
	preview_info_header->Show(false);
	preview_info_label->Show(false);	
	clear_preview_info();
}

/**********************************************************************
   Function: ImageDialog::clear_preview_info()
   Receives: ()
   Returns:  (void)
   Description:
             Cleans out the preview info label by drawing a rectangle
             that matches background colour in its alloted space.
**********************************************************************/

void ImageDialog::clear_preview_info()
{
	int w, h;
	preview_info->GetSize (&w, &h);
	
	wxClientDC dc(preview_info);
	dc.SetPen (*wxTRANSPARENT_PEN);
	dc.SetBrush (wxBrush(GetBackgroundColour(), wxSOLID));
	dc.DrawRectangle (0, 0, w, h);
}
