# -----------------------------------------------------------------------------
# User interface to interpolate between two volume data sets.
#
from chimera.baseDialog import ModelessDialog
import MorphMap

# -----------------------------------------------------------------------------
#
class Morph_Map_Dialog(ModelessDialog):

  title = 'Morph Map'
  name = 'morph map'
  buttons = ('Play', 'Record', 'Close',)
  help = ('morphmap.html', MorphMap)
  
  def fillInUI(self, parent ):

    self.morph_maps = {}        # Writable maps for pairs of end-point maps.
    self.play_handler = None
    self.step_direction = 1     # 1 or -1, current direction when looping
    self.default_f_range = (0.0, 1.0, 0.1)
    self.recording = False

    self.play_stop_button = self.buttonWidgets['Play']
    
    import Tkinter
    from CGLtk import Hybrid
    
    frame = parent
    frame.columnconfigure(0, weight = 1)
    row = 0

    h = Tkinter.Label(parent, text = 'Interpolate between two maps')
    h.grid(row = row, column = 0, sticky = 'w')
    row = row + 1

#    from VolumeViewer.volumemenu import Volume_Menu
    from volumemenu import Volume_Menu
    vm1 = Volume_Menu(parent, 'First map', open_button = True)
    vm1.frame.grid(row = row, column = 0, sticky = 'w')
    row = row + 1
    self.map_menu_1 = vm1

    vm2 = Volume_Menu(parent, 'Second map', open_button = True)
    vm2.frame.grid(row = row, column = 0, sticky = 'w')
    row = row + 1
    self.map_menu_2 = vm2

    self.scale = Hybrid.Scale(parent, 'Fraction ', 0.0, 1.0, 0.01, 0)
    self.scale.frame.grid(row = row, column = 0, sticky = 'ew')
    self.scale.callback(self.scale_changed_cb)
    self.scale.entry.bind('<KeyPress-Return>', self.scale_changed_cb)
    row = row + 1

    fr = Tkinter.Frame(parent)
    fr.grid(row=row, column=0, sticky='w')
    row = row + 1

    fmin, fmax, fstep = self.default_f_range
    fn = Hybrid.Entry(fr, 'Movie start ', 5, fmin)
    fn.frame.grid(row=row, column=0, sticky='w')
    fx = Hybrid.Entry(fr, ' end ', 5, fmax)
    fx.frame.grid(row=row, column=1, sticky='w')
    fs = Hybrid.Entry(fr, ' step ', 5, fstep)
    fs.frame.grid(row=row, column=2, sticky='w')
    self.f_range_variables = (fn.variable, fx.variable, fs.variable)

    um = Hybrid.Checkbutton(parent, 'Undisplay original maps.', True)
    um.button.grid(row = row, column = 0, sticky = 'w')
    row = row + 1
    self.undisplay_maps = um.variable

    mu = Hybrid.Checkbutton_Entries(parent, False, 'Multiplier for second map',
                                    (4, '1.0'))
    mu.frame.grid(row=row, column=0, sticky='w')
    row = row + 1
    self.use_multiplier, self.map_2_multiplier = mu.variables

    am = Hybrid.Checkbutton(parent,
                            'Add to first map instead of interpolating.', False)
    am.button.grid(row = row, column = 0, sticky = 'w')
    row = row + 1
    self.add_maps = am.variable

    #
    # Specify a label width so dialog is not resized for long messages.
    #
    msg = Tkinter.Label(parent, width = 40, anchor = 'w', justify = 'left')
    msg.grid(row = row, column = 0, sticky = 'ew')
    row = row + 1
    self.message_label = msg

  # ---------------------------------------------------------------------------
  #
  def message(self, text):

    self.message_label['text'] = text
    self.message_label.update_idletasks()

  # ---------------------------------------------------------------------------
  #
  def browse_cb(self):

    def open_volume_file(grid_objects):
      # Add data sets to Volume Viewer list
      from VolumeViewer import volume_dialog
      vv = volume_dialog(create = True)
      for go in grid_objects:
        dr = vv.add_region(go)
      # Update volume menu
      if grid_objects:
        vm = self.volume_menu
        vm.update_menu()
        vm.variable.set(dr.full_name())
      
    from VolumeData.opendialog import select_files
    select_files('Open Volume File', open_volume_file)

  # ---------------------------------------------------------------------------
  #
  def scale_changed_cb(self):

    dr1 = self.map_menu_1.data_region()
    dr2 = self.map_menu_2.data_region()
    if dr1 == None or dr2 == None:
      self.message('Need to select two volume data sets.')
      return

    if dr1 == dr2:
      self.message('Two maps are the same.')
      return

    if dr1.data.size != dr2.data.size or dr1.region[:2] != dr2.region[:2]:
      self.message('Map sizes differ.')
      return

    self.message('')

    if self.undisplay_maps.get():
      dr1.unshow()
      dr2.unshow()

    dr = self.writable_map(dr1, dr2)
    self.compute_linear_combination(dr1, dr2, dr)

  # ---------------------------------------------------------------------------
  #
  def writable_map(self, dr1, dr2):

    dr = None
    mm = self.morph_maps
    if (dr1,dr2) in mm:
      dr = mm[(dr1,dr2)]
      # Check that volume is still opened in volume viewer.
      from VolumeViewer import volume_dialog
      vv = volume_dialog()
      if vv and not dr in vv.data_regions:
        dr = None
      else:
        # Check that map size matches current dr1 subregion
        ijk_min, ijk_max = dr1.region[:2]
        dr1_size = tuple(map(lambda a,b: b-a+1, ijk_min, ijk_max))
        if dr.data.size != dr1_size:
          dr = None

    if dr == None:
      try:
        from VolumeViewer import make_writable_volume_copy
      except ImportError:
        from VolumeEraser import make_writable_volume_copy
# TODO:     dr = make_writable_volume_copy(dr1, require_copy = True)
      dr = make_writable_volume_copy(dr1)
      mm[(dr1,dr2)] = mm[(dr2,dr1)] = dr

    return dr

  # ---------------------------------------------------------------------------
  #
  def compute_linear_combination(self, dr1, dr2, dr):

    fmin, fmax, fstep = self.f_range()
    self.scale.set_range(fmin, fmax, fstep)

    f = self.scale.value(default = self.default_f_range[0])
    if self.add_maps.get():
      f1 = 1.0
    else:
      f1 = 1-f
    f2 = f * self.second_map_multiplier()

    origin = dr1.region[0]
    size = dr.data.size
    subsample = step = (1,1,1)
    for c in range(len(dr.components)):
      m = dr.full_matrix(c)
      m1 = dr1.region_matrix(origin, size, subsample, step, c)
      m2 = dr2.region_matrix(origin, size, subsample, step, c)
      mc = f1*m1[:,:,:] + f2*m2[:,:,:]
      try:
        m[:,:,:] = mc
      except TypeError:
        m[:,:,:] = mc.astype(m.typecode())   # Required by Numeric, not NumPy

    try:
      from VolumeViewer import data_values_changed
      data_values_changed(dr.data)
    except ImportError:
      redraw_data_region(dr)    # For Chimera 1.2309 and older

  # ---------------------------------------------------------------------------
  #
  def second_map_multiplier(self):

    if self.use_multiplier.get():
      from CGLtk.Hybrid import float_variable_value
      m = float_variable_value(self.map_2_multiplier, 1.0)
    else:
      m = 1.0
    return m

  # ---------------------------------------------------------------------------
  #
  def Play(self):

    self.play_stop_cb()
  
  # ---------------------------------------------------------------------------
  #
  def play_stop_cb(self):

    from chimera import triggers
    if self.play_handler:
      triggers.deleteHandler('new frame', self.play_handler)
      self.play_handler = None
      self.play_stop_button['text'] = 'Play'
      if self.recording:
        self.finish_recording()
    else:
      h = triggers.addHandler('new frame', self.next_frame_cb, None)
      self.play_handler = h
      self.play_stop_button['text'] = 'Stop'

  # ---------------------------------------------------------------------------
  #
  def next_frame_cb(self, trigger_name, call_data, trigger_data):

    fmin, fmax, fstep = self.f_range()
    fstep *= self.step_direction
    f = self.scale.value(default = fmin)
    next_f = f + fstep
    if next_f >= fmax:
      next_f = fmax
      self.step_direction = -1
    elif next_f <= fmin:
      next_f = fmin
      self.step_direction = 1
      if self.recording:
        self.finish_recording()
    self.scale.set_value(next_f)

  # ---------------------------------------------------------------------------
  #
  def f_range(self):

    from CGLtk.Hybrid import float_variable_value
    dfmin, dfmax, dfstep = self.default_f_range
    vfmin, vfmax, vfstep = self.f_range_variables
    fmin = float_variable_value(vfmin, dfmin)
    fmax = float_variable_value(vfmax, dfmax)
    fstep = float_variable_value(vfstep, dfstep)
    return fmin, fmax, fstep

  # ---------------------------------------------------------------------------
  #
  def Record(self):

      self.record_cb()

  # ---------------------------------------------------------------------------
  #
  def record_cb(self):

    if self.recording:
      return

    # Windows overlapping the graphics window appear in the movie.
    raise_graphics_window()

    fmin, fmax, fstep = self.f_range()
    self.scale.set_value(fmin)
    self.step_direction = 1
    self.recording = True

    from chimera import runCommand
    runCommand('movie record')

    if self.play_handler == None:
      self.play_stop_cb()

  # ---------------------------------------------------------------------------
  #
  def finish_recording(self):

    self.recording = False

    if self.play_handler:
      self.play_stop_cb()
    
    from chimera import runCommand
    runCommand('movie stop')

    d = Save_Movie_Dialog()
    d.enter()

# -----------------------------------------------------------------------------
#
def redraw_data_region(dr):
    
    dr.region_matrices = {}    # Hack to get redraw to happen
    import VolumeViewer
    vv = VolumeViewer.volume_dialog()
    if vv.focus_region == dr:
      vv.redisplay_needed_cb()
    else:
      vv.show_data_regions([dr])

# -----------------------------------------------------------------------------
#
def raise_graphics_window():

  from chimera.tkgui import app
  t = app.winfo_toplevel()
  t.tkraise()

# -----------------------------------------------------------------------------
#
from OpenSave import SaveModeless
class Save_Movie_Dialog(SaveModeless):
  title = 'Save Movie'
  help = ('morphmap.html', MorphMap)
  dismiss = 'Cancel'

  def __init__(self):

    self.default_bit_rate = 6000

    from MovieRecorder import RecorderGUI
    filters = map(lambda f: (f[0], '*.%s' % f[1], '.%s' % f[1]), 
                  RecorderGUI.formats)
    SaveModeless.__init__(self, clientPos = 's', clientSticky = 'w',
                          filters = filters, historyID = "MorphMap")

  # ---------------------------------------------------------------------------
  #
  def fillInUI(self, parent):

    SaveModeless.fillInUI(self, parent)

    from CGLtk import Hybrid
    br = Hybrid.Entry(self.clientArea, 'Bit rate (Kb/sec):', 6,
                      self.default_bit_rate)
    br.frame.grid(row = 0, column = 0, sticky = 'w')
    self.bit_rate = br.variable

  # ---------------------------------------------------------------------------
  #
  def Apply(self):

    path, format = self.getPathsAndTypes()[0]
    
    from MovieRecorder import RecorderGUI
    for fmt, f in RecorderGUI.command_formats.items():
      if f[0] == format:
        break
    from CGLtk.Hybrid import float_variable_value
    bit_rate = float_variable_value(self.bit_rate, self.default_bit_rate)

    from chimera import runCommand
    runCommand('movie encode mformat %s bitrate %f output "%s"'
               % (fmt, bit_rate, path))

  # ---------------------------------------------------------------------------
  #
  def Cancel(self):

    from chimera import runCommand
    runCommand('movie reset')
    self.Close()
  
# -----------------------------------------------------------------------------
#
def morph_map_dialog(create = False):

  from chimera import dialogs
  return dialogs.find(Morph_Map_Dialog.name, create=create)
  
# -----------------------------------------------------------------------------
#
def show_morph_map_dialog():

  from chimera import dialogs
  return dialogs.display(Morph_Map_Dialog.name)

# -----------------------------------------------------------------------------
#
from chimera import dialogs
dialogs.register(Morph_Map_Dialog.name, Morph_Map_Dialog, replace = True)
