import pyrvapi
import os,sys,subprocess, time
import common

flush_time=0.0
output_dir=None
uri_prefix=None

class rvbase(object):
  def __init__(self, ID, parent=None, param=None):
    self.ID=ID
    self.parent=parent
    self.param=param
    self.children=[]
  def Remove(self):
    if not pyrvapi.rvapi_remove_widget(self.ID):
      common.Warning('pyrvapi could not remove widget {0} {1}'.format(type(self).__name__,self.ID))


def Document(ID, outdir, title=None, mode=1, jsuri=None, viewer=None, header=None, \
             layout=7, helpfname=None, htmlfname=None, taskfname=None, i2=False):
  if title is None:  title=ID
  if jsuri is None or viewer is None:
    if not os.environ.has_key("CCP4"):
      common.Warning('pyrvapi could not initialize document because jsrview directory not defined.')
      return None
    if jsuri is None:
      jsuri = os.path.join(os.environ["CCP4"], "share", "jsrview")
    if viewer is None:
      if not i2:
        viewer = os.path.join(os.environ["CCP4"], "libexec", "jsrview")
      #  viewer = ['ccp4-python', os.path.join(os.environ["CCP4"], "share", "ccp4i2", "bin", "browser.py")]
      #### for testing purposes only
      #viewer = os.path.join("/bin/firefox")
  if i2:  layout = 0
  if not pyrvapi.rvapi_init_document(ID, outdir, title, mode, layout, jsuri, helpfname, htmlfname, taskfname):
    common.Warning('pyrvapi could not initialize document.')
    return None
  global output_dir
  output_dir = outdir
  if header and not pyrvapi.rvapi_add_header(header):
    common.Warning('pyrvapi could not add header.')
  if viewer and os.path.isfile(viewer):
    if isinstance(viewer,basestring):  viewer=[viewer]
    fnull = open(os.devnull, 'w')
    proc=subprocess.Popen( viewer + [os.path.join(outdir, "index.html")], stderr=fnull, stdout=fnull)
  return rvdocument(ID)


class rvdocument(rvbase):

  def Tab(self, ID, title=None, opened=True):
    if title is None:  title=ID
    if not pyrvapi.rvapi_add_tab(ID, title, opened):
      common.Warning('pyrvapi could not add tab.')
      return None
    self.children.append( rvtab(ID,self) )
    return self.children[-1]

  def Grid(self, ID, filling=True, row=-1, col=0, rowspan=1, colspan=1):
    if not pyrvapi.rvapi_add_grid(ID, filling, self.ID, row, col, rowspan, colspan):
      common.Warning('pyrvapi could not add grid.')
      return None
    self.children.append( rvgrid(ID,self) )
    return self.children[-1]


class rvtab(rvbase):

  def Section(self, ID, title=None, row=-1, col=0, rowspan=1, colspan=1, opened=True):
    if title is None:  title=ID
    if not pyrvapi.rvapi_add_section(ID, title, self.ID, row, col, rowspan, colspan, opened):
      common.Warning('pyrvapi could not add section.')
      return None
    self.children.append( rvsection(ID,self) )
    return self.children[-1]

  def Tree(self, ID, title=None, row=-1, col=0, rowspan=1, colspan=1):
    if title is None:  title=ID
    if not pyrvapi.rvapi_add_tree_widget(ID, title, self.ID, row, col, rowspan, colspan):
      common.Warning('pyrvapi could not add section.')
      return None
    self.children.append( rvtree(ID,self) )
    return self.children[-1]

class rvgrid(rvtab):
  pass

class rvtree(rvtab):
  def Section(self, ID, title=None, row=-1, col=0, rowspan=1, colspan=1, opened=True):
    section = rvtab.Section(self, ID, title, row, col, rowspan, colspan, opened)
    if title is None:  title=ID
    if not pyrvapi.rvapi_set_tree_node(self.ID, section.ID, title, "auto", ""):
      common.Warning('pyrvapi could not add section to tree.')
      return None
    return section


class rvsection(rvbase):
  def Text(self, text, append=True, row=None, col=None, rowspan=None, colspan=None, flush=False):
    if row is not None or col is not None or rowspan is not None or colspan is not None:
      if append:
        append=False
      if row is None:  row = -1
      if col is None:  row = 0
      if rowspan is None:  rowspan = 1
      if colspan is None:  colspan = 1
    if append:
      if not pyrvapi.rvapi_append_text(text, self.ID):
        common.Warning('pyrvapi could not add text.')
    else:
      if not pyrvapi.rvapi_add_text(text, self.ID, row,col,rowspan,colspan):
        common.Warning('pyrvapi could not add text.')
    if flush:  Flush()

  def getgraph(self,graph):
    # if existing graph is not inputted then a new graph will be created
    if graph is None:
      return self.Graph('g_'+self.ID)
    elif isinstance(graph,basestring):
      return self.Graph(graph)
    else:
      return graph

  def PlotLine(self, X, Y, graph=None, block=None, plot=None ):
    graph = self.getgraph(graph)
    return graph.PlotLine(X, Y, block, plot)

  def Plot(self, ID, x_label, y_label=None, title=None, graph=None, block=None):
    graph = self.getgraph(graph)
    return graph.Plot(ID, x_label, y_label, title, block)

  def Graph(self, ID):
    if not pyrvapi.rvapi_append_loggraph(ID, self.ID):
      common.Warning('pyrvapi could not add graph.')
      return None
    self.children.append( rvgraph(ID,self) )
    return self.children[-1]

  def DataFile(self, fname, ftype, title=None, ID=None, folded=True, flush=False):
    if ID is None:
      ID = 'df_'+self.ID+str(len(self.children)+1)
    self.children.append( rvfileblock(ID,self) )
    self.children[-1].DataFile(fname, ftype, title, folded=folded)
    if flush:  Flush()
    return self.children[-1]


class rvfileblock(rvbase):
  empty = True

  def DataFile(self, fname, ftype, title=None, folded=True, flush=False):
    if uri_prefix:
      common_prefix = os.path.commonprefix( [output_dir, fname] )
      if common_prefix:
        fname = os.path.join( uri_prefix, os.path.relpath( fname, common_prefix ) )
    if self.empty:
      if not pyrvapi.rvapi_add_data(self.ID, title, fname, ftype, self.parent.ID, -1, 0, 1, 1, folded):
        common.Warning('pyrvapi could not add data file.')
      else:
        self.empty=False
    else:
      if not pyrvapi.rvapi_append_to_data(self.ID, fname, ftype):
        common.Warning('pyrvapi could not append data file.')
    if flush:  Flush()



# the structure appears to be graph -> data block -> plot -> plotline

class rvgraph(rvbase):

  def getblock(self,block):
    if block is None:
      return self.DataBlock('db_'+self.ID)
    elif isinstance(block,basestring):
      return self.DataBlock(block)
    else:
      return block

  def PlotLine(self, X, Y, block=None, plot=None ):
    block = self.getblock(block)
    return block.PlotLine(X, Y, plot)

  def Plot(self, ID, x_label, y_label=None, title=None, block=None):
    if block is None:
      block = ID
    block = self.getblock(block)
    return block.Plot(ID, x_label, y_label, title)

  def DataBlock(self, ID, title=None):
    if title is None:  title = ID
    if not pyrvapi.rvapi_add_graph_data(ID, self.ID, title):
      common.Warning('pyrvapi could not add data block.')
      return None
    self.children.append( rvdatablock(ID,self,0) )
    return self.children[-1]


class rvdatablock(rvbase):

  def PlotLine(self, X, Y, plot=None):
    if plot is None:
      plot = self.Plot(self.ID, X[0], Y[0])
    elif isinstance(plot,basestring):
      plot = self.Plot(plot, X[0], Y[0])
    return plot.PlotLine(X[1:], Y[1:], self.ID)

  def Plot(self, ID, x_label, y_label=None, title=None):
    if title is None:  title = ID
    if y_label is None: y_label = title
    if not pyrvapi.rvapi_add_graph_plot(ID, self.parent.ID, title, x_label, y_label):
      common.Warning('pyrvapi could not add data block.')
      return None
    self.children.append( rvplot(ID,self) )
    return self.children[-1]


class rvplot(rvbase):

  # Y = (y_legend, y_header, [y_data]) ; y_header and/or [y_data] are optional (the same holds for X)
  def PlotLine(self, X, Y, ID=None, flush=False):
    n = str( self.parent.param + 1 )
    if ID is None:
      ID = self.parent.ID+n
    Xhead_omit, Yhead_omit = len(X)<2 or not isinstance(X[1],basestring), len(Y)<2 or not isinstance(Y[1],basestring)
    X_header, Y_header = X[0]  if Xhead_omit  else X[1], Y[0]  if Xhead_omit  else Y[1]
    if not pyrvapi.rvapi_add_graph_dataset("x"+n, self.parent.ID, self.parent.parent.ID, X[0], X_header):
      common.Warning('pyrvapi could not add X dataset.')
      return None
    #Y_header = X[0]  if Yhead_omit  else Y[1]
    if not pyrvapi.rvapi_add_graph_dataset("y"+n, self.parent.ID, self.parent.parent.ID, Y[0], Y_header):
      common.Warning('pyrvapi could not add Y dataset.')
      return None
    if not pyrvapi.rvapi_add_plot_line(self.ID, self.parent.ID, self.parent.parent.ID, "x"+n, "y"+n):
      common.Warning('pyrvapi could not add plot line.')
      return None
    self.children.append( rvplotline(ID,self,n) )
    self.parent.param += 1
    x_data_ind, y_data_ind = 2  if not Xhead_omit  else 1, 2 if not Yhead_omit  else 1
    if len(X)>x_data_ind and len(Y)>y_data_ind:
      self.children[-1].Data(X[x_data_ind],Y[y_data_ind],flush=flush if flush is not None else True)
    return self.children[-1]


class rvplotline(rvbase):

  def singleData(self,xy,val,typ=float):
    n=self.param
    # implemented, although there seems to be no difference between real and int plotting?
    if typ is int:
      if not pyrvapi.rvapi_add_graph_int(xy+n, self.parent.parent.ID, self.parent.parent.parent.ID, int(val)):
        common.Warning('pyrvapi could not add {0} integer data {1}.'.format(xy,val))
    else:
      if not pyrvapi.rvapi_add_graph_real(xy+n, self.parent.parent.ID, self.parent.parent.parent.ID, float(val), "%g"):
        common.Warning('pyrvapi could not add {0} real data {1}.'.format(xy,val))

  def Data(self,x,y, x_type=float, y_type=float, flush=False):
    try:
      length = min( len(x),len(y) )
    except TypeError:
      self.singleData("x",x,x_type)
      self.singleData("y",y,y_type)
    else:
      for i in range( length ):
        self.singleData("x",x[i],x_type)
        self.singleData("y",y[i],y_type)
    if flush:  Flush()


def AppendContent(file_path, parent_id):
  if not pyrvapi.rvapi_append_content(file_path, True, parent_id):
    common.Warning('pyrvapi could not append content.')


def Flush(ignore_timing_restr=False):
  # workaround for flush flooding issues of jsrview: flush frequency set to at least 1 second
  global flush_time
  if ignore_timing_restr or (time.time()-flush_time)>0.5:
    if not pyrvapi.rvapi_flush():
      common.Warning('pyrvapi could not flush.')
    else:
      flush_time = time.time()
