/*=========================================================================

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/VolViewCopyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "vtkKWCameraLightWidget.h"

#include "vtkKWApplication.h"
#include "vtkKWChangeColorButton.h"
#include "vtkKWCheckButton.h"
#include "vtkKWEvent.h"
#include "vtkKWFrame.h"
#include "vtkKWLabel.h"
#include "vtkKWMenu.h"
#include "vtkKWCheckButtonWithLabel.h"
#include "vtkKWFrameWithLabel.h"
#include "vtkKWMenuButtonWithLabel.h"
#include "vtkKWMenuButton.h"
#include "vtkKWScaleWithEntry.h"
#include "vtkKWTkUtilities.h"
#include "vtkKWVolumeWidget.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"

//----------------------------------------------------------------------------

#define VTK_KW_CAMERA_LIGHT_WIDGET_PREVIEW_SIZE 81
#define VTK_KW_CAMERA_LIGHT_WIDGET_PREVIEW_BG   127
#define VTK_KW_CAMERA_LIGHT_WIDGET_PREVIEW_BD   64

//----------------------------------------------------------------------------

vtkStandardNewMacro(vtkKWCameraLightWidget);
vtkCxxRevisionMacro(vtkKWCameraLightWidget, "$Revision: 1.44 $");

//----------------------------------------------------------------------------
vtkKWCameraLightWidget::vtkKWCameraLightWidget()
{
  this->VolumeWidget   = NULL;
  this->Lights         = NULL;
  this->NumberOfLights = 0;
  this->CurrentLight   = -1;
  this->MovingLight    = 0;

  // UI

  this->CameraLightFrame = vtkKWFrameWithLabel::New();
  
  this->WidgetFrame = vtkKWFrame::New();

  this->ActiveLightMenu = vtkKWMenuButtonWithLabel::New();

  this->LightVisibilityCheckButton = vtkKWCheckButtonWithLabel::New();

  this->LightColorButton = vtkKWChangeColorButton::New();

  this->LightIntensityScale = vtkKWScaleWithEntry::New();
  
  this->LightPreviewLabel = vtkKWLabel::New();

}

//----------------------------------------------------------------------------
vtkKWCameraLightWidget::~vtkKWCameraLightWidget()
{
  this->SetVolumeWidget(NULL);

  this->DeleteLights();

  if (this->CameraLightFrame)
    {
    this->CameraLightFrame->Delete();
    this->CameraLightFrame = NULL;
    }
  
  if (this->WidgetFrame)
    {
    this->WidgetFrame->Delete();
    this->WidgetFrame = NULL;
    }

  if (this->ActiveLightMenu)
    {
    this->ActiveLightMenu->Delete();
    this->ActiveLightMenu = NULL;
    }

  if (this->LightVisibilityCheckButton)
    {
    this->LightVisibilityCheckButton->Delete();
    this->LightVisibilityCheckButton = NULL;
    }

  if (this->LightColorButton)
    {
    this->LightColorButton->Delete();
    this->LightColorButton = NULL;
    }

  if (this->LightIntensityScale)
    {
    this->LightIntensityScale->Delete();
    this->LightIntensityScale = NULL;
    }
  
  if (this->LightPreviewLabel)
    {
    this->LightPreviewLabel->Delete();
    this->LightPreviewLabel = NULL;
    }
}

//----------------------------------------------------------------------------
void vtkKWCameraLightWidget::SetVolumeWidget(vtkKWVolumeWidget *widget)
{
  if (this->VolumeWidget == widget)
    {
    return;
    }

  this->VolumeWidget = widget;
  this->Modified();

  this->Update();
}

//----------------------------------------------------------------------------
void vtkKWCameraLightWidget::CreateWidget()
{
  // Check if already created

  if (this->IsCreated())
    {
    vtkErrorMacro(<< this->GetClassName() << " already created");
    return;
    }

  // Call the superclass to create the whole widget

  this->Superclass::CreateWidget();

  vtkKWFrame *frame;
  ostrstream tk_cmd;

  int nb_synced_widgets = 0;
  const char *synced_widgets[100];
  
  // --------------------------------------------------------------
  // Camera light frame

  this->CameraLightFrame->SetParent(this);
  this->CameraLightFrame->Create();
  this->CameraLightFrame->SetLabelText("Light Controls");

  tk_cmd << "pack " << this->CameraLightFrame->GetWidgetName()
         << " -side top -anchor w -expand y -fill x -padx 0 -pady 0" << endl;

  frame = this->CameraLightFrame->GetFrame();

  // --------------------------------------------------------------
  // Widget frame: the UI except the preview
 
  this->WidgetFrame->SetParent(frame);
  this->WidgetFrame->Create();
  
  tk_cmd << "pack " << this->WidgetFrame->GetWidgetName()
         << " -side left -anchor nw -expand y -fill x -padx 0 -pady 0" << endl;

  // --------------------------------------------------------------
  // Current light
 
  this->ActiveLightMenu->SetParent(this->WidgetFrame);
  this->ActiveLightMenu->Create();
  this->ActiveLightMenu->GetLabel()->SetText("Active light:");

  tk_cmd << "pack " << this->ActiveLightMenu->GetWidgetName()
         << " -side top -anchor w -padx 2 -pady 2" << endl;

  synced_widgets[nb_synced_widgets++] = 
    this->ActiveLightMenu->GetLabel()->GetWidgetName();

  // --------------------------------------------------------------
  // Light visibility
 
  this->LightVisibilityCheckButton->SetParent(this->WidgetFrame);
  this->LightVisibilityCheckButton->Create();
  this->LightVisibilityCheckButton->GetLabel()->SetText("Visibility:");
  this->LightVisibilityCheckButton->GetWidget()->SetText("");
  this->LightVisibilityCheckButton->GetWidget()->SetCommand(
    this, "LightVisibilityCallback");
  
  tk_cmd << "pack " << this->LightVisibilityCheckButton->GetWidgetName()
         << " -side top -anchor w -padx 2 -pady 2" << endl;

  synced_widgets[nb_synced_widgets++] = 
    this->LightVisibilityCheckButton->GetLabel()->GetWidgetName();

  // --------------------------------------------------------------
  // Light color
 
  this->LightColorButton->SetParent(this->WidgetFrame);
  this->LightColorButton->LabelOutsideButtonOn();
  this->LightColorButton->Create();
  this->LightColorButton->GetLabel()->SetText("Color:");
  this->LightColorButton->SetCommand(this, "LightColorCallback");
  this->LightColorButton->SetDialogTitle("Color Of Light");
  
  tk_cmd << "pack " << this->LightColorButton->GetWidgetName()
         << " -side top -anchor w -padx 2 -pady 2" << endl;

  synced_widgets[nb_synced_widgets++] = 
    this->LightColorButton->GetLabel()->GetWidgetName();

  // --------------------------------------------------------------
  // Light intensity
 
  this->LightIntensityScale->SetParent(this->WidgetFrame);
  this->LightIntensityScale->Create();
  this->LightIntensityScale->SetResolution(0.01);
  this->LightIntensityScale->SetRange(0, 1);
  this->LightIntensityScale->SetEntryWidth(5);
  this->LightIntensityScale->SetLabelText("Intensity:");
  this->LightIntensityScale->SetCommand(this, "LightIntensityCallback");
  
  tk_cmd << "pack " << this->LightIntensityScale->GetWidgetName()
         << " -side top -anchor w -padx 2 -pady 2 -fill x -expand y" << endl;

  synced_widgets[nb_synced_widgets++] = 
    this->LightIntensityScale->GetLabel()->GetWidgetName();

  // --------------------------------------------------------------
  // Light preview
 
  this->LightPreviewLabel->SetParent(frame);
  this->LightPreviewLabel->Create();

  tk_cmd << "pack " << this->LightPreviewLabel->GetWidgetName()
         << " -side right -anchor ne -padx 2 -pady 2" << endl;

  this->LightPreviewLabel->SetBinding(
    "<ButtonPress>", this, "MouseButtonPressCallback %x %y");

  this->LightPreviewLabel->SetBinding(
    "<Motion>", this, "MouseMoveCallback %x %y");

  this->LightPreviewLabel->SetBinding(
    "<ButtonRelease>", this, "MouseButtonReleaseCallback %x %y");
  
  // --------------------------------------------------------------
  // Pack 

  tk_cmd << ends;
  this->Script(tk_cmd.str());
  tk_cmd.rdbuf()->freeze(0);

  // Cosmetic: make some labels the same width so that the UI looks
  // more "aligned". A grid can not be used here.

  vtkKWTkUtilities::SynchroniseLabelsMaximumWidth(
    this->GetApplication()->GetMainInterp(), 
    nb_synced_widgets, synced_widgets, "-anchor w");

 // Update according to the current widget

  this->Update();
}

//----------------------------------------------------------------------------
void vtkKWCameraLightWidget::Update()
{
  // Update enable state
  
  this->UpdateEnableState();

  if (!this->IsCreated())
    {
    return;
    }

  // Update light lists

  this->UpdateLights();

  // Update current parameters

  this->UpdateCurrentParameters();

  // Update preview

  this->UpdatePreview();
}

//----------------------------------------------------------------------------
void vtkKWCameraLightWidget::UpdateCurrentParameters()
{
  if (!this->IsCreated() ||
      !this->NumberOfLights ||
      this->CurrentLight < 0 || this->CurrentLight >= this->NumberOfLights)
    {
    return;
    }

  // Active light

  if (this->ActiveLightMenu)
    {
    vtkKWMenuButton *menu = this->ActiveLightMenu->GetWidget();
    char buffer[10];
    sprintf(buffer, "%d", this->CurrentLight);
    if (menu->GetMenu()->HasItem(buffer))
      {
      menu->SetValue(buffer);
      }
    }

  // Light visibility

  this->LightVisibilityCheckButton->GetWidget()->SetSelectedState(
    this->Lights[this->CurrentLight].Visibility);

  // Light color

  this->LightColorButton->SetColor(this->Lights[this->CurrentLight].Color);

  // Light intensity

  if (this->LightIntensityScale->GetValue() != 
      this->Lights[this->CurrentLight].Intensity)
    {
    this->LightIntensityScale->SetValue(
      this->Lights[this->CurrentLight].Intensity);
    }
}

//----------------------------------------------------------------------------
void vtkKWCameraLightWidget::UpdateEnableState()
{
  this->Superclass::UpdateEnableState();

  this->PropagateEnableState(this->CameraLightFrame);
  this->PropagateEnableState(this->WidgetFrame);
  this->PropagateEnableState(this->ActiveLightMenu);
  this->PropagateEnableState(this->LightVisibilityCheckButton);
  this->PropagateEnableState(this->LightColorButton);
  this->PropagateEnableState(this->LightIntensityScale);
  this->PropagateEnableState(this->LightPreviewLabel);
}

//----------------------------------------------------------------------------
void vtkKWCameraLightWidget::DeleteLights()
{
  if (this->Lights)
    {
    delete [] this->Lights;
    this->Lights = NULL;
    }
  this->NumberOfLights = 0;
}

//----------------------------------------------------------------------------
void vtkKWCameraLightWidget::UpdateLights()
{
  if (!this->VolumeWidget)
    {
    return;
    }

  int i;
  int lights_have_been_reallocated = 0;

  // Reallocate the lights if needed, and update the active light menu

  if (this->VolumeWidget->GetNumberOfLights() != this->NumberOfLights)
    {
    this->DeleteLights();
    this->NumberOfLights = this->VolumeWidget->GetNumberOfLights();
    if (this->NumberOfLights)
      {
      this->Lights = new vtkKWCameraLightWidget::Light[this->NumberOfLights];
      lights_have_been_reallocated = 1;
      }
    
    if (this->IsCreated() && this->ActiveLightMenu)
      {
      char buffer[10];
      vtkKWMenuButton *menubutton = this->ActiveLightMenu->GetWidget();
      menubutton->GetMenu()->DeleteAllItems();
      for (i = 0; i < this->NumberOfLights; i++)
        {
        sprintf(buffer, "%d", i);
        menubutton->GetMenu()->AddRadioButton(
          buffer, this, "ActiveLightCallback");
        }
      }
    }

  if (this->NumberOfLights <= 1)
    {
    if (this->ActiveLightMenu)
      {
      this->ActiveLightMenu->SetEnabled(0);
      }
    if (this->LightVisibilityCheckButton)
      {
      this->LightVisibilityCheckButton->SetEnabled(0);
      }
    }

  // Get the lights info

  double *fptr, pos[3], factor;
  int half_size = VTK_KW_CAMERA_LIGHT_WIDGET_PREVIEW_SIZE / 2;

  for (i = 0; i < this->NumberOfLights; i++)
    {
    fptr = this->VolumeWidget->GetLightPosition(i);
    if (fptr)
      {
      pos[0] = fptr[0];
      pos[1] = fptr[1];
      pos[2] = fptr[2];
      vtkMath::Normalize(pos);
      factor = (double)half_size / pos[2];
      this->Lights[i].Position[0] = 
        vtkMath::Round(pos[0] * factor) + half_size;
      this->Lights[i].Position[1] = 
        half_size - vtkMath::Round(pos[1] * factor);
      }
    else
      {
      this->Lights[i].Position[0] = 0;
      this->Lights[i].Position[1] = 0;
      }
    this->Lights[i].Visibility = this->VolumeWidget->GetLightVisibility(i);
    fptr = this->VolumeWidget->GetLightColor(i);
    if (fptr)
      {
      this->Lights[i].Color[0] = fptr ? fptr[0] : 0.0;
      this->Lights[i].Color[1] = fptr ? fptr[1] : 0.0;
      this->Lights[i].Color[2] = fptr ? fptr[2] : 0.0;
      }
    else
      {
      this->Lights[i].Color[0] = 0.0;
      this->Lights[i].Color[1] = 0.0;
      this->Lights[i].Color[2] = 0.0;
      }
    this->Lights[i].Intensity = this->VolumeWidget->GetLightIntensity(i);
    }

  if (lights_have_been_reallocated)
    {
    // Force it to be an invalid value, so that SetActiveLight really updates
    // the UI if the current light was already 0 (but a different light)
    this->CurrentLight = -1; 
    this->SetActiveLight(0);
    }
}

//----------------------------------------------------------------------------
void vtkKWCameraLightWidget::UpdatePreview()
{
  if (!this->IsCreated())
    {
    return;
    }

  // Get the color of the background

  double b_r, b_g, b_b;
  this->CameraLightFrame->GetFrame()->GetBackgroundColor(&b_r, &b_g, &b_b);
  b_r *= 255.0;
  b_g *= 255.0;
  b_b *= 255.0;

  // Prepare the image

  unsigned char image[VTK_KW_CAMERA_LIGHT_WIDGET_PREVIEW_SIZE *
                      VTK_KW_CAMERA_LIGHT_WIDGET_PREVIEW_SIZE * 3];
  unsigned char *ptr = image;

  int preview_size = VTK_KW_CAMERA_LIGHT_WIDGET_PREVIEW_SIZE;
  int half_size = preview_size / 2;
  double dist2_threshold = (half_size - 2) * (half_size - 2);
  double dist2_threshold2 = half_size * half_size;
  double dist2;

  // Draw the hemisphere

  int i, j;
  for (i = 0; i < preview_size; i++)
    {
    for (j = 0; j < preview_size; j++)
      {
      dist2 = 
        (double)(i - half_size) * (double)(i - half_size) + 
        (double)(j - half_size) * (double)(j - half_size);

      if (dist2 < dist2_threshold)
        {
        *ptr++ = VTK_KW_CAMERA_LIGHT_WIDGET_PREVIEW_BG;
        *ptr++ = VTK_KW_CAMERA_LIGHT_WIDGET_PREVIEW_BG;
        *ptr++ = VTK_KW_CAMERA_LIGHT_WIDGET_PREVIEW_BG;
        }
      else if (dist2 < dist2_threshold2)
        {
        *ptr++ = VTK_KW_CAMERA_LIGHT_WIDGET_PREVIEW_BD;
        *ptr++ = VTK_KW_CAMERA_LIGHT_WIDGET_PREVIEW_BD;
        *ptr++ = VTK_KW_CAMERA_LIGHT_WIDGET_PREVIEW_BD;
        }
      else
        {
        *ptr++ = (int)b_r;
        *ptr++ = (int)b_g;
        *ptr++ = (int)b_b;
        }
      }
    }

  // Place the lights

  int x, y, x2, y2;
  int k;
  
  for (i = 0; i < this->NumberOfLights; i++)
    {

    x = this->Lights[i].Position[0];
    y = this->Lights[i].Position[1];

    // Draw the light (hollow if not visible)

    for (j = -2; j < 2; j++)
      {
      for (k = -2; k < 2; k++)
        {
        if (this->Lights[i].Visibility ||
            (!this->Lights[i].Visibility &&
             (j == -2 || j == 1 || k == -2 || k == 1)))
          {
          x2 = x + k;
          y2 = y + j;
          if (x2 >= 0 && x2 < preview_size && y2 >= 0 && y2 < preview_size)
            {
            ptr = image + (y2 * preview_size + x2) * 3;
            *ptr++ = (unsigned char)(this->Lights[i].Color[0] * 255);
            *ptr++ = (unsigned char)(this->Lights[i].Color[1] * 255);
            *ptr++ = (unsigned char)(this->Lights[i].Color[2] * 255);
            }
          }
        }
      }

    // Add one more outline if it is selected

    if (i == this->CurrentLight )
      {
      for (j = -3; j < 3; j++)
        {
        for (k = -3; k < 3; k++)
          {
          if (j == -3 || j == 2 || k == -3 || k == 2)
            {
            x2 = x + k;
            y2 = y + j;
            if (x2 >= 0 && x2 < preview_size && y2 >= 0 && y2 < preview_size)
              {
              ptr = image + ((y + j) * preview_size + (x + k)) * 3;
              *ptr++ = (unsigned char)(this->Lights[i].Color[0] * 255);
              *ptr++ = (unsigned char)(this->Lights[i].Color[1] * 255);
              *ptr++ = (unsigned char)(this->Lights[i].Color[2] * 255);
              }
            }
          }
        }
      }
    }

  this->LightPreviewLabel->SetImageToPixels(
    image, preview_size, preview_size, 3);
}

//----------------------------------------------------------------------------
void vtkKWCameraLightWidget::MouseButtonPressCallback(int x, int y)
{
  if (!this->IsCreated())
    {
    return;
    }

  // Find the new current light

  int i;
  for (i = 0; i < this->NumberOfLights; i++)
    {
    if (abs(x - this->Lights[i].Position[0]) < 4 &&
        abs(y - this->Lights[i].Position[1]) < 4)
      {
      this->MovingLight = 1;
      this->SetActiveLight(i);
      int light = this->CurrentLight;
      this->InvokeEvent(vtkKWEvent::LightActiveChangedEvent, &light);
      return;
      }
    }
}

//----------------------------------------------------------------------------
void vtkKWCameraLightWidget::MouseMoveCallback(int x, int y)
{
  if (!this->MovingLight)
    {
    return;
    }

  int half_size = VTK_KW_CAMERA_LIGHT_WIDGET_PREVIEW_SIZE / 2;

  double dist = sqrt((double)(x - half_size) * (double)(x - half_size) + 
                    (double)(y - half_size) * (double)(y - half_size));
  
  if (dist < half_size)
    {
    this->Lights[this->CurrentLight].Position[0] = x;
    this->Lights[this->CurrentLight].Position[1] = y;
    this->UpdatePreview();
    }
}

//----------------------------------------------------------------------------
void vtkKWCameraLightWidget::MouseButtonReleaseCallback(
  int vtkNotUsed(x), int vtkNotUsed(y))
{
  if (!this->MovingLight)
    {
    return;
    }

  // Eventually we should call MouseMoveCallback(x, y) to update the 
  // CurrentLight position given x, y, but we assume that a
  // MouseMoveCallback event was already
  // generated for that position (if not, then we are at the same spot)

  int half_size = VTK_KW_CAMERA_LIGHT_WIDGET_PREVIEW_SIZE / 2;

  double pos[3];
  pos[0] = this->Lights[this->CurrentLight].Position[0] - half_size;
  pos[1] = half_size - this->Lights[this->CurrentLight].Position[1];
  pos[2] = half_size;
  vtkMath::Normalize(pos);

  double args[4];
  args[0] = this->CurrentLight;
  args[1] = pos[0];
  args[2] = pos[1];
  args[3] = pos[2];
  this->InvokeEvent(vtkKWEvent::LightPositionChangedEvent, args);
    
  this->MovingLight = 0;
}

//----------------------------------------------------------------------------
void vtkKWCameraLightWidget::SetActiveLight(int idx)
{
  if (this->CurrentLight != idx && idx >= 0 && idx < this->NumberOfLights)
    {
    this->CurrentLight = idx;
    this->UpdateCurrentParameters();
    this->UpdatePreview();
    }
}

//----------------------------------------------------------------------------
void vtkKWCameraLightWidget::ActiveLightCallback()
{
  if (!this->IsCreated())
    {
    return;
    }

  const char *value = this->ActiveLightMenu->GetWidget()->GetValue();
  if (value && *value)
    {
    this->SetActiveLight(atoi(value));
    int light = this->CurrentLight;
    this->InvokeEvent(vtkKWEvent::LightActiveChangedEvent, &light);
    }
}

//----------------------------------------------------------------------------
void vtkKWCameraLightWidget::LightVisibilityCallback(int state)
{
  if (!this->IsCreated())
    {
    return;
    }

  int count = 0, i;
  
  // Can't turn off all the lights; otherwise we get a default headlight,
  // which we don't want

  if (!state)
    {
    for (i = 0; i < this->NumberOfLights; i++)
      {
      if (this->Lights[i].Visibility)
        {
        count++;
        }
      }
    if (count <= 1)
      {
      this->LightVisibilityCheckButton->GetWidget()->SetSelectedState(1);
      return;
      }
    }
  
  this->Lights[this->CurrentLight].Visibility =
    this->LightVisibilityCheckButton->GetWidget()->GetSelectedState();
  
  this->UpdatePreview();
  
  int args[2];
  args[0] = this->CurrentLight;
  args[1] = this->Lights[this->CurrentLight].Visibility;
  this->InvokeEvent(vtkKWEvent::LightVisibilityChangedEvent, args);
}

//----------------------------------------------------------------------------
void vtkKWCameraLightWidget::LightColorCallback(double r, double g, double b)
{
  if (!this->IsCreated())
    {
    return;
    }

  this->Lights[this->CurrentLight].Color[0] = r;
  this->Lights[this->CurrentLight].Color[1] = g;
  this->Lights[this->CurrentLight].Color[2] = b;
  
  this->UpdatePreview();
  
  double args[4];
  args[0] = this->CurrentLight;
  args[1] = r;
  args[2] = g;
  args[3] = b;
  this->InvokeEvent(vtkKWEvent::LightColorChangedEvent, args);
}

//----------------------------------------------------------------------------
void vtkKWCameraLightWidget::LightIntensityCallback(double value)
{
  this->Lights[this->CurrentLight].Intensity = value;
  
  double args[2];
  args[0] = this->CurrentLight;
  args[1] = this->Lights[this->CurrentLight].Intensity;
  this->InvokeEvent(vtkKWEvent::LightIntensityChangedEvent, args);
}

//----------------------------------------------------------------------------
void vtkKWCameraLightWidget::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  os << indent << "VolumeWidget: " << this->VolumeWidget << endl;
  os << indent << "CameraLightFrame: " << this->CameraLightFrame << endl;
}
