/*
 * Created on Sep 13, 2004
 * Created by Olivier Chalouhi
 * Copyright (C) 2004, 2005, 2006 Aelitis, All Rights Reserved.
 *
 * 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 * AELITIS, SAS au capital de 46,603.30 euros
 * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
 *
 */
package org.gudy.azureus2.ui.swt.views.stats;


import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.impl.DownloadManagerRateController;
import org.gudy.azureus2.core3.global.GlobalManager;
import org.gudy.azureus2.core3.global.GlobalManagerStats;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.peer.PEPeerManager;
import org.gudy.azureus2.core3.stats.transfer.OverallStats;
import org.gudy.azureus2.core3.stats.transfer.StatsFactory;
import org.gudy.azureus2.core3.util.AERunnable;
import org.gudy.azureus2.core3.util.DisplayFormatters;
import org.gudy.azureus2.ui.swt.Messages;
import org.gudy.azureus2.ui.swt.Utils;
import org.gudy.azureus2.ui.swt.components.BufferedLabel;
import org.gudy.azureus2.ui.swt.components.Legend;
import org.gudy.azureus2.ui.swt.components.graphics.PingGraphic;
import org.gudy.azureus2.ui.swt.components.graphics.Plot3D;
import org.gudy.azureus2.ui.swt.components.graphics.SpeedGraphic;
import org.gudy.azureus2.ui.swt.components.graphics.ValueFormater;
import org.gudy.azureus2.ui.swt.mainwindow.Colors;
import org.gudy.azureus2.ui.swt.plugins.UISWTView;
import org.gudy.azureus2.ui.swt.plugins.UISWTViewEvent;
import org.gudy.azureus2.ui.swt.pluginsimpl.UISWTViewCoreEventListener;

import com.aelitis.azureus.core.AzureusCore;
import com.aelitis.azureus.core.AzureusCoreRunningListener;
import com.aelitis.azureus.core.AzureusCoreFactory;
import com.aelitis.azureus.core.speedmanager.SpeedManager;
import com.aelitis.azureus.core.speedmanager.SpeedManagerLimitEstimate;
import com.aelitis.azureus.core.speedmanager.SpeedManagerPingMapper;
import com.aelitis.azureus.core.speedmanager.SpeedManagerPingSource;
import com.aelitis.azureus.core.speedmanager.SpeedManagerPingZone;

/**
 * 
 */
public class TransferStatsView
	implements UISWTViewCoreEventListener
{
	public static final String MSGID_PREFIX = "TransferStatsView";

	private static final int MAX_DISPLAYED_PING_MILLIS		= 1199;	// prevents us hitting 1200 and resulting in graph expanding to 1400
	private static final int MAX_DISPLAYED_PING_MILLIS_DISP	= 1200;	// tidy display
	
	private GlobalManager		global_manager;
	private GlobalManagerStats 	stats;
	private SpeedManager 		speedManager;
  
	private OverallStats totalStats;
  
	private Composite mainPanel;  
  
	private Composite blahPanel;
	private BufferedLabel asn,estUpCap,estDownCap;
	private BufferedLabel uploadBiaser;
	
	private Composite 		connectionPanel;
	private Label			upload_label, connection_label;
	private SpeedGraphic	upload_graphic;
	private SpeedGraphic	connection_graphic;
	
	private Composite generalPanel;
	private BufferedLabel nowUp, nowDown, sessionDown, sessionUp, session_ratio, sessionTime, totalDown, totalUp, total_ratio, totalTime;
  
	private Group autoSpeedPanel;
	private StackLayout autoSpeedPanelLayout;
	private Composite autoSpeedInfoPanel;
	private Composite autoSpeedDisabledPanel;
	private PingGraphic pingGraph;

	private plotView[]	plot_views;
	private zoneView[] 	zone_views;
  
	private limitToTextHelper	limit_to_text = new limitToTextHelper();
  
	private final DecimalFormat formatter = new DecimalFormat( "##.#" );
  
	private boolean	initialised;

	private UISWTView swtView;
  
  
  public TransferStatsView() {
  	AzureusCoreFactory.addCoreRunningListener(new AzureusCoreRunningListener() {
			public void azureusCoreRunning(AzureusCore core) {
				global_manager = core.getGlobalManager();
				stats = global_manager.getStats();
				speedManager = core.getSpeedManager();
				totalStats = StatsFactory.getStats();
			}
		});
    pingGraph = PingGraphic.getInstance();
    
  }
  
  private void initialize(Composite composite) {
    
    mainPanel = new Composite(composite,SWT.NULL);
    GridLayout mainLayout = new GridLayout();
    mainPanel.setLayout(mainLayout);
    
    AzureusCoreFactory.addCoreRunningListener(new AzureusCoreRunningListener() {
			public void azureusCoreRunning(AzureusCore core) {
				Utils.execSWTThread(new AERunnable() {
					public void runSupport() {
						if (mainPanel == null || mainPanel.isDisposed()) {
							return;
						}
						createGeneralPanel();
						createConnectionPanel();
						createCapacityPanel();
						createAutoSpeedPanel();
						
						initialised	= true;
					}
				});
			}
		});
  }

  private void createGeneralPanel() {
    generalPanel = new Composite(mainPanel,SWT.BORDER);
    GridData generalPanelData = new GridData(GridData.FILL_HORIZONTAL);
    generalPanel.setLayoutData(generalPanelData);
    
    GridLayout panelLayout = new GridLayout();
    panelLayout.numColumns = 5;
    panelLayout.makeColumnsEqualWidth = true;
    generalPanel.setLayout(panelLayout);
    
    GridData gridData;
    
    Label lbl = new Label(generalPanel,SWT.NULL);
    
    lbl = new Label(generalPanel,SWT.NULL);
    Messages.setLanguageText(lbl,"SpeedView.stats.downloaded");
    
    lbl = new Label(generalPanel,SWT.NULL);
    Messages.setLanguageText(lbl,"SpeedView.stats.uploaded");
    
    lbl = new Label(generalPanel,SWT.NULL);
    Messages.setLanguageText(lbl,"SpeedView.stats.ratio");
    
    lbl = new Label(generalPanel,SWT.NULL);
    Messages.setLanguageText(lbl,"SpeedView.stats.uptime");
    
    lbl = new Label(generalPanel,SWT.NULL);
    lbl = new Label(generalPanel,SWT.NULL);
    lbl = new Label(generalPanel,SWT.NULL);
    lbl = new Label(generalPanel,SWT.NULL);
    lbl = new Label(generalPanel,SWT.NULL);
    
    /////// NOW /////////
    lbl = new Label(generalPanel,SWT.NULL);
    Messages.setLanguageText(lbl,"SpeedView.stats.now");
    
    nowDown = new BufferedLabel(generalPanel,SWT.NULL);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    nowDown.setLayoutData(gridData);
    
    nowUp = new BufferedLabel(generalPanel,SWT.NULL);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    nowUp.setLayoutData(gridData);
    
    lbl = new Label(generalPanel,SWT.NULL);
    lbl = new Label(generalPanel,SWT.NULL);
    
    
    //////// SESSION ////////
    lbl = new Label(generalPanel,SWT.NULL);
    Messages.setLanguageText(lbl,"SpeedView.stats.session");
    sessionDown = new BufferedLabel(generalPanel,SWT.NULL);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    sessionDown.setLayoutData(gridData);
    
    sessionUp = new BufferedLabel(generalPanel,SWT.NULL);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    sessionUp.setLayoutData(gridData);
    
    session_ratio = new BufferedLabel(generalPanel,SWT.NULL);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    session_ratio.setLayoutData(gridData);    
    
    sessionTime = new BufferedLabel(generalPanel,SWT.NULL);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    sessionTime.setLayoutData(gridData);
    
    
    ///////// TOTAL ///////////
    lbl = new Label(generalPanel,SWT.NULL);
    Messages.setLanguageText(lbl,"SpeedView.stats.total");
    
    totalDown = new BufferedLabel(generalPanel,SWT.NULL);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    totalDown.setLayoutData(gridData);
    
    totalUp = new BufferedLabel(generalPanel,SWT.NULL);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    totalUp.setLayoutData(gridData);
    
    total_ratio = new BufferedLabel(generalPanel,SWT.NULL);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    total_ratio.setLayoutData(gridData);
    
    totalTime = new BufferedLabel(generalPanel,SWT.NULL);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    totalTime.setLayoutData(gridData);
  }
  
  private void
  createCapacityPanel()
  {
	  blahPanel = new Composite(mainPanel,SWT.NONE);
	  GridData blahPanelData = new GridData(GridData.FILL_HORIZONTAL);
	  blahPanel.setLayoutData(blahPanelData);

	  GridLayout panelLayout = new GridLayout();
	  panelLayout.numColumns = 8;
	  blahPanel.setLayout(panelLayout);


	  Label label;
	  GridData gridData;

	  label = new Label(blahPanel,SWT.NONE);
	  Messages.setLanguageText(label,"SpeedView.stats.asn");    
	  asn = new BufferedLabel(blahPanel,SWT.NONE);
	  gridData = new GridData(GridData.FILL_HORIZONTAL);
	  asn.setLayoutData(gridData);

	  label = new Label(blahPanel,SWT.NONE);
	  Messages.setLanguageText(label,"SpeedView.stats.estupcap");    
	  estUpCap = new BufferedLabel(blahPanel,SWT.NONE);
	  gridData = new GridData(GridData.FILL_HORIZONTAL);
	  estUpCap.setLayoutData(gridData);

	  label = new Label(blahPanel,SWT.NONE);
	  Messages.setLanguageText(label,"SpeedView.stats.estdowncap");    
	  estDownCap = new BufferedLabel(blahPanel,SWT.NONE);
	  gridData = new GridData(GridData.FILL_HORIZONTAL);
	  estDownCap.setLayoutData(gridData);

	  label = new Label(blahPanel,SWT.NONE);
	  label = new Label(blahPanel,SWT.NONE);
	  
	  label = new Label(blahPanel,SWT.NONE);
	  Messages.setLanguageText(label,"SpeedView.stats.upbias");    
	  uploadBiaser = new BufferedLabel(blahPanel,SWT.NONE);
	  gridData = new GridData(GridData.FILL_HORIZONTAL);
	  gridData.horizontalSpan = 7;
	  uploadBiaser.setLayoutData(gridData);

  }
  
  private void
  createConnectionPanel()
  {
	  connectionPanel = new Composite(mainPanel,SWT.NONE);
	  GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
	  connectionPanel.setLayoutData(gridData);

	  GridLayout panelLayout = new GridLayout();
	  panelLayout.numColumns = 2;
	  connectionPanel.setLayout(panelLayout);

	  Composite conn_area = new Composite( connectionPanel, SWT.NULL );
	  gridData = new GridData(GridData.FILL_HORIZONTAL);
	  conn_area.setLayoutData(gridData);

	  panelLayout = new GridLayout();
	  panelLayout.numColumns = 2;
	  conn_area.setLayout(panelLayout);

	  Label label = new Label( conn_area, SWT.NULL );
	  Messages.setLanguageText( label, "SpeedView.stats.con" );
	  
	  connection_label = new Label( conn_area, SWT.NULL );
	  gridData = new GridData(GridData.FILL_HORIZONTAL);
	  connection_label.setLayoutData(gridData);

	  Composite upload_area = new Composite( connectionPanel, SWT.NULL );
	  gridData = new GridData(GridData.FILL_HORIZONTAL);
	  upload_area.setLayoutData(gridData);

	  panelLayout = new GridLayout();
	  panelLayout.numColumns = 2;
	  upload_area.setLayout(panelLayout);

	  label = new Label( upload_area, SWT.NULL );
	  Messages.setLanguageText( label, "SpeedView.stats.upload" );

	  upload_label = new Label( upload_area, SWT.NULL );
	  gridData = new GridData(GridData.FILL_HORIZONTAL);
	  upload_label.setLayoutData(gridData);


	  	// connections
	  
	  Canvas connection_canvas = new Canvas(connectionPanel,SWT.NO_BACKGROUND);
	  gridData = new GridData(GridData.FILL_BOTH);
	  gridData.heightHint = 200;
	  connection_canvas.setLayoutData(gridData);
	  connection_graphic = 
		  SpeedGraphic.getInstance(
			new ValueFormater()
			{
			    public String 
			    format(int value) 
			    {
			         return( String.valueOf( value ));
			    }
			});
	  
	  connection_graphic.initialize(connection_canvas);
	  Color[] colors = connection_graphic.colors;

	  connection_graphic.setLineColors( colors );
	  
	  	// upload queued
	  
	  Canvas upload_canvas = new Canvas(connectionPanel,SWT.NO_BACKGROUND);
	  gridData = new GridData(GridData.FILL_BOTH);
	  upload_canvas.setLayoutData(gridData);
	  upload_graphic = 
		  SpeedGraphic.getInstance(
			new ValueFormater()
			{
			    public String 
			    format(int value) 
			    {
			         return DisplayFormatters.formatByteCountToKiBEtc(value);
			    }
			});
	  
	  upload_graphic.initialize(upload_canvas);
	  
  }
  
  private void createAutoSpeedPanel() {
    autoSpeedPanel = new Group(mainPanel,SWT.NONE);
    GridData generalPanelData = new GridData(GridData.FILL_BOTH);
    autoSpeedPanel.setLayoutData(generalPanelData);
    Messages.setLanguageText(autoSpeedPanel,"SpeedView.stats.autospeed", new String[]{ String.valueOf( MAX_DISPLAYED_PING_MILLIS_DISP )});
    
    
    autoSpeedPanelLayout = new StackLayout();
    autoSpeedPanel.setLayout(autoSpeedPanelLayout);
    
    autoSpeedInfoPanel = new Composite(autoSpeedPanel,SWT.NULL);
    autoSpeedInfoPanel.setLayoutData(new GridData(GridData.FILL_BOTH));
    GridLayout layout = new GridLayout();
    layout.numColumns = 8;
    layout.makeColumnsEqualWidth = true;
    autoSpeedInfoPanel.setLayout(layout);
       
    Canvas pingCanvas = new Canvas(autoSpeedInfoPanel,SWT.NO_BACKGROUND);
    GridData gridData = new GridData(GridData.FILL_BOTH);
    gridData.horizontalSpan = 4;
    pingCanvas.setLayoutData(gridData);
    
    pingGraph.initialize(pingCanvas);
    
    TabFolder folder = new TabFolder(autoSpeedInfoPanel, SWT.LEFT);
    gridData = new GridData(GridData.FILL_BOTH);
    gridData.horizontalSpan = 4;
    folder.setLayoutData(gridData);
    folder.setBackground(Colors.background);
    
    ValueFormater speed_formatter = 
    	new ValueFormater()
	    {
	    	public String 
	    	format(
	    		int value) 
	    	{
	    		return( DisplayFormatters.formatByteCountToKiBEtc( value ));
	    	}
	    };
	    
    ValueFormater time_formatter = 
    	new ValueFormater()
	    {
	    	public String 
	    	format(
	    		int value) 
	    	{
	    		return( value + " ms" );
	    	}
	    };
		    
    ValueFormater[] formatters = new ValueFormater[]{ speed_formatter, speed_formatter, time_formatter };
    
    String[] labels = new String[]{ "up", "down", "ping" };
    
    SpeedManagerPingMapper[] mappers = speedManager.getMappers();
    
    plot_views	= new plotView[mappers.length];
    zone_views	= new zoneView[mappers.length];
    
    for (int i=0;i<mappers.length;i++){
    
    	SpeedManagerPingMapper mapper = mappers[i];
    	
	    TabItem plot_item = new TabItem(folder, SWT.NULL);
	    
	    plot_item.setText( "Plot " + mapper.getName());
	    
	    Canvas plotCanvas = new Canvas(folder,SWT.NO_BACKGROUND);
	    gridData = new GridData(GridData.FILL_BOTH);
	    plotCanvas.setLayoutData(gridData);
	    
	    plot_views[i] = new plotView( mapper, plotCanvas, labels, formatters );
	    
	    plot_item.setControl( plotCanvas );
	    
	    TabItem zones_item = new TabItem(folder, SWT.NULL);
	    zones_item.setText( "Zones " + mapper.getName() );
	    
	    Canvas zoneCanvas = new Canvas(folder,SWT.NO_BACKGROUND);
	    gridData = new GridData(GridData.FILL_BOTH);
	    zoneCanvas.setLayoutData(gridData);
	
	    zone_views[i] = new zoneView( mapper, zoneCanvas, labels, formatters );
	    
	    zones_item.setControl( zoneCanvas );
    }
    
    autoSpeedDisabledPanel = new Composite(autoSpeedPanel,SWT.NULL);
    autoSpeedDisabledPanel.setLayout(new GridLayout());
    Label disabled = new Label(autoSpeedDisabledPanel,SWT.NULL);
    disabled.setEnabled(false);
    Messages.setLanguageText(disabled,"SpeedView.stats.autospeed.disabled");
    disabled.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_CENTER | GridData.FILL_HORIZONTAL));
    
    autoSpeedPanelLayout.topControl = speedManager.isAvailable() ? autoSpeedInfoPanel : autoSpeedDisabledPanel;
    
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    gridData.horizontalSpan = 8;
    
	Legend.createLegendComposite(
			autoSpeedInfoPanel,
    		PingGraphic.colors,
    		new String[]{
        			"TransferStatsView.legend.pingaverage",        			
        			"TransferStatsView.legend.ping1",        			
        			"TransferStatsView.legend.ping2",
    				"TransferStatsView.legend.ping3" },
    		gridData );
  }
  
  private void delete() {
		Utils.disposeComposite(generalPanel);
		Utils.disposeComposite(blahPanel);
		
		if ( upload_graphic != null ){
			upload_graphic.dispose();
		}
		
		if ( connection_graphic != null ){
			connection_graphic.dispose();
		}
		
		if (pingGraph != null) {
			pingGraph.dispose();
		}

		if (plot_views != null) {
			for (int i = 0; i < plot_views.length; i++) {

				plot_views[i].dispose();
			}
		}

		if (zone_views != null) {
			for (int i = 0; i < zone_views.length; i++) {

				zone_views[i].dispose();
			}
		}
	}

  private Composite getComposite() {
    return mainPanel;
  }
  
  
  
  private void refresh() {

	  if ( !initialised ){
		  return;
	  }
	  refreshGeneral();

	  refreshCapacityPanel();

	  refreshConnectionPanel();

	  refreshPingPanel();
  }

  private void refreshGeneral() {
	if ( stats == null ){
		return;
	}
	
    int now_prot_down_rate = stats.getProtocolReceiveRate();
    int now_prot_up_rate = stats.getProtocolSendRate();
    
    int now_total_down_rate = stats.getDataReceiveRate() + now_prot_down_rate;
    int now_total_up_rate = stats.getDataSendRate() + now_prot_up_rate;
    
    float now_perc_down = (float)(now_prot_down_rate *100) / (now_total_down_rate==0?1:now_total_down_rate);
    float now_perc_up = (float)(now_prot_up_rate *100) / (now_total_up_rate==0?1:now_total_up_rate);

    nowDown.setText(DisplayFormatters.formatByteCountToKiBEtcPerSec( now_total_down_rate ) +
                    "  (" + DisplayFormatters.formatByteCountToKiBEtcPerSec( now_prot_down_rate ) +
                    ", " +formatter.format( now_perc_down )+ "%)" );
    
    nowUp.setText(DisplayFormatters.formatByteCountToKiBEtcPerSec( now_total_up_rate ) +
                  "  (" + DisplayFormatters.formatByteCountToKiBEtcPerSec( now_prot_up_rate ) +
                  ", " +formatter.format( now_perc_up )+ "%)" );

    ///////////////////////////////////////////////////////////////////////

    long session_prot_received = stats.getTotalProtocolBytesReceived();
    long session_prot_sent = stats.getTotalProtocolBytesSent();
      
    long session_total_received = stats.getTotalDataBytesReceived() + session_prot_received;
    long session_total_sent = stats.getTotalDataBytesSent() + session_prot_sent;

    float session_perc_received = (float)(session_prot_received *100) / (session_total_received==0?1:session_total_received);
    float session_perc_sent = (float)(session_prot_sent *100) / (session_total_sent==0?1:session_total_sent);

    sessionDown.setText(DisplayFormatters.formatByteCountToKiBEtc( session_total_received ) +
                        "  (" + DisplayFormatters.formatByteCountToKiBEtc( session_prot_received ) +
                        ", " +formatter.format( session_perc_received )+ "%)" );
    
    sessionUp.setText(DisplayFormatters.formatByteCountToKiBEtc( session_total_sent ) +
                      "  (" + DisplayFormatters.formatByteCountToKiBEtc( session_prot_sent ) +
                      ", " +formatter.format( session_perc_sent )+ "%)" );
    
    ////////////////////////////////////////////////////////////////////////
    
    if (totalStats != null) {
      totalDown.setText(DisplayFormatters.formatByteCountToKiBEtc( totalStats.getDownloadedBytes() ));
      totalUp.setText(DisplayFormatters.formatByteCountToKiBEtc( totalStats.getUploadedBytes() ));
  
      sessionTime.setText( DisplayFormatters.formatETA( totalStats.getSessionUpTime() ) );
      totalTime.setText( DisplayFormatters.formatETA( totalStats.getTotalUpTime() ) );
    
      long dl_bytes = totalStats.getDownloadedBytes();
    
      long t_ratio_raw = (1000* totalStats.getUploadedBytes() / (dl_bytes==0?1:dl_bytes) );
      long s_ratio_raw = (1000* session_total_sent / (session_total_received==0?1:session_total_received) );
      
      String t_ratio = "";
      String s_ratio = "";
  
      String partial = String.valueOf(t_ratio_raw % 1000);
      while (partial.length() < 3) {
        partial = "0" + partial;
      }
      t_ratio = (t_ratio_raw / 1000) + "." + partial;
      
      partial = String.valueOf(s_ratio_raw % 1000);
      while (partial.length() < 3) {
        partial = "0" + partial;
      }
      s_ratio = (s_ratio_raw / 1000) + "." + partial;
      
      
      total_ratio.setText( t_ratio );
      session_ratio.setText( s_ratio );
    }
  }  
  
  private void
  refreshCapacityPanel()
  {
	  if ( speedManager == null ){
		  return;
	  }
	  
	  asn.setText(speedManager.getASN());

	  estUpCap.setText(limit_to_text.getLimitText(speedManager.getEstimatedUploadCapacityBytesPerSec()));

	  estDownCap.setText(limit_to_text.getLimitText(speedManager.getEstimatedDownloadCapacityBytesPerSec()));
	  
	  uploadBiaser.setText( DownloadManagerRateController.getString());
  }
  
  private void
  refreshConnectionPanel()
  {
	  if ( global_manager == null ){
		  return;
	  }
	  
	  int	total_connections	= 0;
	  int	total_con_queued	= 0;
	  int	total_con_blocked	= 0;
	  int	total_con_unchoked	= 0;
	  
	  int	total_data_queued	= 0;
	  
	  List<DownloadManager> dms = global_manager.getDownloadManagers();
	  
	  for ( DownloadManager dm: dms ){
		  
		  PEPeerManager pm = dm.getPeerManager();
		  
		  if ( pm != null ){
			  
			  total_data_queued += pm.getBytesQueuedForUpload();
			  
			  total_connections += pm.getNbPeers() + pm.getNbSeeds();
			  
			  total_con_queued 	+= pm.getNbPeersWithUploadQueued();
			  total_con_blocked	+= pm.getNbPeersWithUploadBlocked();
			  
			  total_con_unchoked += pm.getNbPeersUnchoked();
		  }
		  
	  }
	  
	  Messages.setLanguageText( 
			 connection_label, "SpeedView.stats.con_details",
			 new String[]{ String.valueOf(total_connections), String.valueOf(total_con_unchoked), String.valueOf(total_con_queued), String.valueOf(total_con_blocked) });
	  
	  connection_graphic.addIntsValue( new int[]{ total_connections, total_con_unchoked, total_con_queued, total_con_blocked });
	  
	  Messages.setLanguageText( 
			 upload_label, "SpeedView.stats.upload_details",
			 new String[]{ DisplayFormatters.formatByteCountToKiBEtc( total_data_queued )});
	  
	  upload_graphic.addIntValue( total_data_queued );
	  
	  upload_graphic.refresh();
	  connection_graphic.refresh();
  }
  
  private void refreshPingPanel() {
  	if (speedManager == null) {
  		return;
  	}
    if(speedManager.isAvailable()){// && speedManager.isEnabled()) {
      autoSpeedPanelLayout.topControl = autoSpeedInfoPanel;
      autoSpeedPanel.layout();
   
      pingGraph.refresh();        
      for (int i=0;i<plot_views.length;i++){

    	  plot_views[i].refresh();
      }

      for (int i=0;i<zone_views.length;i++){

    	  zone_views[i].refresh();
      }

    } else {
      autoSpeedPanelLayout.topControl = autoSpeedDisabledPanel;
      autoSpeedPanel.layout();
    }  
  }
  
  /* (non-Javadoc)
   * @see org.gudy.azureus2.ui.swt.views.stats.PeriodicViewUpdate#periodicUpdate()
   */
  public void periodicUpdate() {
  	if (speedManager == null) {
  		return;
  	}
    if(speedManager.isAvailable()){// && speedManager.isEnabled()) {
      SpeedManagerPingSource sources[] = speedManager.getPingSources();
      if(sources.length > 0) {
        int[] pings = new int[sources.length];
        for(int i = 0 ; i < sources.length ; i++) {
        	int	ping = sources[i].getPingTime();
        	
        	ping = Math.min( ping, MAX_DISPLAYED_PING_MILLIS );
        	
        	pings[i] = ping;
        }
        pingGraph.addIntsValue(pings);
        
        if (plot_views != null) {
          for (int i=0;i<plot_views.length;i++){
          	
          	plot_views[i].update();
          }
        }
        
        if (zone_views != null) {
          for (int i=0;i<zone_views.length;i++){
          	
          	zone_views[i].update();
          }
        }
      }
    }
  }
  

  protected String
  getMapperTitle(
		SpeedManagerPingMapper mapper )
  {
	  if ( mapper.isActive()){
		  
		  SpeedManagerLimitEstimate up_1 		= mapper.getEstimatedUploadLimit(false);
		  SpeedManagerLimitEstimate up_2 		= mapper.getEstimatedUploadLimit(true);
		  SpeedManagerLimitEstimate down_1 	= mapper.getEstimatedDownloadLimit(false);
		  SpeedManagerLimitEstimate down_2 	= mapper.getEstimatedDownloadLimit(true);
		  
		  return( "ul=" + DisplayFormatters.formatByteCountToKiBEtc(up_1.getBytesPerSec()) + ":" + DisplayFormatters.formatByteCountToKiBEtc(up_2.getBytesPerSec())+ 
				  ",dl=" + DisplayFormatters.formatByteCountToKiBEtc(down_1.getBytesPerSec()) + ":" + DisplayFormatters.formatByteCountToKiBEtc(down_2.getBytesPerSec()) + 
				  ",mr=" + DisplayFormatters.formatDecimal( mapper.getCurrentMetricRating(),2));
	  }
	  
	  return( "" );
  }
  
  class
  plotView
  {
	  private SpeedManagerPingMapper	mapper;
	  private Plot3D 					plotGraph;
	  
	  protected
	  plotView(
		  SpeedManagerPingMapper	_mapper,
		  Canvas					_canvas,
		  String[]					_labels,
		  ValueFormater[]			_formatters )
	  {
		  mapper	= _mapper;
		  
		  plotGraph = new Plot3D( _labels, _formatters );

		  plotGraph.setMaxZ( MAX_DISPLAYED_PING_MILLIS );
		  
		  plotGraph.initialize(_canvas);
	  }
	  
	  protected void
	  update()
	  {
		  int[][]	history = mapper.getHistory();

		  plotGraph.update(history);
		  
		  plotGraph.setTitle( getMapperTitle( mapper ));
	  }
	  
	  protected void
	  refresh()
	  {
		  plotGraph.refresh();
	  }
	  
	  protected void
	  dispose()
	  {
		  plotGraph.dispose();
	  }
  }
  
  class
  zoneView
  	implements ParameterListener
  {
	  private SpeedManagerPingMapper	mapper;
	  
	  private SpeedManagerPingZone[] zones = new SpeedManagerPingZone[0];
	  
	  private Canvas			canvas;
	  
	  private ValueFormater[] formatters;

	  private String[] labels;

	  private String	title = "";
	  
	  private int 	refresh_count;
	  private int 	graphicsUpdate;
	  private Point old_size;

	  protected Image buffer_image;

	  protected
	  zoneView(
		  SpeedManagerPingMapper		_mapper,
		  Canvas 						_canvas,
		  String[]						_labels,
		  ValueFormater[]				_formatters )
	  {
		  mapper		= _mapper;
		  canvas		= _canvas;
		  labels		= _labels;
		  formatters	= _formatters;
		  
		  COConfigurationManager.addAndFireParameterListener( "Graphics Update", this ); 
	  }

	  public void 
	  parameterChanged(
		  String name ) 
	  {
		  graphicsUpdate = COConfigurationManager.getIntParameter( name );

	  }

	  protected void
	  update()
	  {
		  zones	= mapper.getZones();
		  
		  title = getMapperTitle( mapper );
	  }
	  
	  private void
	  refresh()
	  {
		  if ( canvas.isDisposed()){

			  return;
		  }

		  Rectangle bounds = canvas.getClientArea();

		  if ( bounds.height < 30 || bounds.width  < 100 || bounds.width > 2000 || bounds.height > 2000 ){
			  
			  return;
		  }
		  
		  boolean size_changed = (old_size == null || old_size.x != bounds.width || old_size.y != bounds.height);

		  old_size = new Point(bounds.width,bounds.height);

		  refresh_count++;

		  if ( refresh_count > graphicsUpdate ){

			  refresh_count = 0;
		  }

		  if ( refresh_count == 0 || size_changed ){

			  if ( buffer_image != null && ! buffer_image.isDisposed()){

				  buffer_image.dispose();
			  }
			  
			  buffer_image = draw( bounds );
		  }

		  if ( buffer_image != null ){
			  
			  GC gc = new GC( canvas );
	
			  gc.drawImage( buffer_image, bounds.x, bounds.y );
	
			  gc.dispose();   
		  }
	  }
	  
	  private Image
	  draw(
		Rectangle	bounds )
	  {
		  final int	PAD_TOP		= 10;
		  final int	PAD_BOTTOM	= 10;
		  final int	PAD_RIGHT	= 10;
		  final int	PAD_LEFT	= 10;

		  int usable_width 	= bounds.width - PAD_LEFT - PAD_RIGHT;
		  int usable_height	= bounds.height - PAD_TOP - PAD_BOTTOM;

		  Image image = new Image( canvas.getDisplay(), bounds );

		  GC gc = new GC( image );

		  try {
		  	gc.setAntialias( SWT.ON );
		  } catch (Exception e) {
		  	// Ignore ERROR_NO_GRAPHICS_LIBRARY error or any others
		  }

		  int font_height 	= gc.getFontMetrics().getHeight();
		  int char_width 	= gc.getFontMetrics().getAverageCharWidth();


		  Color[] colours = plot_views[0].plotGraph.getColours();

		  int	max_x 		= 0;
		  int	max_y 		= 0;

		  if ( zones.length > 0 ){
			 
			  int	max_metric	= 0;
	
			  for (int i=0;i<zones.length;i++){
	
				  SpeedManagerPingZone zone = zones[i];
	
				  int	metric 	= zone.getMetric();
	
				  if ( metric > 0 ){
	
					  max_metric = Math.max( max_metric, metric );
	
					  max_x = Math.max( max_x, zone.getUploadEndBytesPerSec());
					  max_y = Math.max( max_y, zone.getDownloadEndBytesPerSec());
				  }
			  }

			  if ( max_x > 0 && max_y > 0 ){
			  
				  double x_ratio = (double)usable_width/max_x;
				  double y_ratio = (double)usable_height/max_y;
				  
				  List<Object[]>	texts = new ArrayList<Object[]>();
				  
				  for (int i=0;i<zones.length;i++){
		
					  SpeedManagerPingZone zone = zones[i];
		
					  int	metric 	= zone.getMetric();
					  int	x1		= zone.getUploadStartBytesPerSec();
					  int	y1 		= zone.getDownloadStartBytesPerSec();
					  int	x2 		= zone.getUploadEndBytesPerSec();
					  int	y2		= zone.getDownloadEndBytesPerSec();
							  
					  if ( metric > 0 ){
		
						  int	colour_index = (int)((float)metric*colours.length/max_metric);
		
						  if ( colour_index >= colours.length ){
		
							  colour_index = colours.length-1;
						  }
		
						  gc.setBackground( colours[colour_index] );
		
						  int	x 		= PAD_LEFT + (int)(x1*x_ratio);
						  int	y 		= PAD_TOP  + (int)(y1*y_ratio);
						  int	width 	= (int)Math.ceil((x2-x1+1)*x_ratio);
						  int	height	= (int)Math.ceil((y2-y1+1)*y_ratio );
						  
						  int	y_draw = usable_height + PAD_TOP + PAD_TOP - y - height;
						  
						  gc.fillRectangle( x, y_draw, width, height );
						  		
						  int	text_metric = zone.getMetric();
						  
						  String text = String.valueOf( metric );
							  
						  int	text_width = text.length()*char_width + 4;
						  
						  if ( width >= text_width && height >= font_height ){
							  

							  Rectangle text_rect = 
								new Rectangle(
										x + ((width-text_width)/2), 
										y_draw + ((height-font_height)/2), 
										text_width, font_height );
									
							  	// check for overlap with existing and delete older
							  
							  Iterator<Object[]> it = texts.iterator();
							  
							  while( it.hasNext()){
								  
								  Object[]	old = it.next();
								  
								  Rectangle old_coords = (Rectangle)old[1];
								  
								  if ( old_coords.intersects( text_rect )){
									
									  it.remove();
								  }
							  }
							  
							  texts.add( new Object[]{ new Integer( text_metric ), text_rect });  
						  }
					  }
				  }
				  
				  	// only do the last 100 texts as things get a little cluttered
				  
				  int	text_num = texts.size();
				  
				  for (int i=(text_num>100?(text_num-100):0);i<text_num;i++){
					  
					  Object[]	entry = texts.get(i);
					  
					  String	str = String.valueOf(entry[0]);
					  
					  Rectangle	rect = (Rectangle)entry[1];
					  
					  gc.drawText(str, rect.x, rect.y, SWT.DRAW_TRANSPARENT );
				  }
			  }
		  }
		  
		  	// x axis

		  int x_axis_left_x = PAD_LEFT;
		  int x_axis_left_y = usable_height + PAD_TOP;

		  int x_axis_right_x = PAD_LEFT + usable_width;
		  int x_axis_right_y = x_axis_left_y;


		  gc.drawLine( x_axis_left_x, x_axis_left_y, x_axis_right_x, x_axis_right_y );
		  gc.drawLine( usable_width, x_axis_right_y - 4, x_axis_right_x, x_axis_right_y );
		  gc.drawLine( usable_width, x_axis_right_y + 4, x_axis_right_x, x_axis_right_y );

		  for (int i=1;i<10;i++){
			  
			  int	x = x_axis_left_x + ( x_axis_right_x - x_axis_left_x )*i/10;
			  
			  gc.drawLine( x, x_axis_left_y, x, x_axis_left_y+4 );
		  }
		  
		  SpeedManagerLimitEstimate le = mapper.getEstimatedUploadLimit( false );
		  
		  if ( le != null ){
			  
			  gc.setForeground(Colors.grey );
			  
			  int[][] segs = le.getSegments();
			  
			  if ( segs.length > 0 ){
				  
				  int	max_metric 	= 0;
				  int	max_pos		= 0;
				  
				  for (int i=0;i<segs.length;i++){
					  
					  int[]	seg = segs[i];
					  
					  max_metric 	= Math.max( max_metric, seg[0] );
					  max_pos 		= Math.max( max_pos, seg[2] );
				  }
				  
				  double	metric_ratio 	= max_metric==0?1:((float)50/max_metric);
				  double	pos_ratio 		= max_pos==0?1:((float)usable_width/max_pos);
				  
				  int	prev_x	= 1;
				  int	prev_y	= 1;
				  
				  for (int i=0;i<segs.length;i++){
					  
					  int[]	seg = segs[i];
					  
					  int	next_x 	= (int)((seg[1] + (seg[2]-seg[1])/2)*pos_ratio) + 1;
					  int	next_y	= (int)((seg[0])*metric_ratio) + 1;
					  
					  gc.drawLine(
								x_axis_left_x + prev_x,
								x_axis_left_y - prev_y,
								x_axis_left_x + next_x,
								x_axis_left_y - next_y );
														
					  prev_x = next_x;
					  prev_y = next_y;
				  }
			  }
			  
			  gc.setForeground( Colors.black );
		  }
		  
		  SpeedManagerLimitEstimate[] bad_up = mapper.getBadUploadHistory();
		  
		  if ( bad_up.length > 0 ){
			  
			  gc.setLineWidth( 3 );
			  
			  gc.setForeground( Colors.red );

			  for (int i=0;i<bad_up.length;i++){
				  
				  int speed = bad_up[i].getBytesPerSec();
				  
				  int	x = max_x == 0?0:(speed * usable_width / max_x);
			  
				  gc.drawLine(
							x_axis_left_x + x,
							x_axis_left_y - 0,
							x_axis_left_x + x,
							x_axis_left_y - 10 );

			  }
			  
			  gc.setForeground( Colors.black );
			  
			  gc.setLineWidth( 1 );
		  }
		  
		  String x_text = labels[0] + " - " + formatters[0].format( max_x+1 );

		  gc.drawText( 	x_text, 
				  		x_axis_right_x - 20 - x_text.length()*char_width, 
				  		x_axis_right_y - font_height - 2, 
				  		SWT.DRAW_TRANSPARENT );

		  	// y axis

		  int y_axis_bottom_x = PAD_LEFT;
		  int y_axis_bottom_y = usable_height + PAD_TOP;

		  int y_axis_top_x 	= PAD_LEFT;
		  int y_axis_top_y 	= PAD_TOP;

		  gc.drawLine( y_axis_bottom_x, y_axis_bottom_y, y_axis_top_x, y_axis_top_y );

		  gc.drawLine( y_axis_top_x-4, y_axis_top_y+PAD_TOP,	y_axis_top_x, y_axis_top_y );
		  gc.drawLine( y_axis_top_x+4, y_axis_top_y+PAD_TOP,	y_axis_top_x, y_axis_top_y );

		  for (int i=1;i<10;i++){
			  
			  int	y = y_axis_bottom_y + ( y_axis_top_y - y_axis_bottom_y )*i/10;
			  
			  gc.drawLine( y_axis_bottom_x, y, y_axis_bottom_x-4, y );
		  }
		  
		  le = mapper.getEstimatedDownloadLimit( false );
		  
		  if ( le != null ){
			  
			  gc.setForeground(Colors.grey );
			  
			  int[][] segs = le.getSegments();
			  
			  if ( segs.length > 0 ){
				  
				  int	max_metric 	= 0;
				  int	max_pos		= 0;
				  
				  for (int i=0;i<segs.length;i++){
					  
					  int[]	seg = segs[i];
					  
					  max_metric 	= Math.max( max_metric, seg[0] );
					  max_pos 		= Math.max( max_pos, seg[2] );
				  }
				  
				  double	metric_ratio 	= max_metric==0?1:((float)50/max_metric);
				  double	pos_ratio 		= max_pos==0?1:((float)usable_height/max_pos);
				  
				  int	prev_x	= 1;
				  int	prev_y	= 1;
				  
				  for (int i=0;i<segs.length;i++){
					  
					  int[]	seg = segs[i];
					  
					  int	next_x	= (int)((seg[0])*metric_ratio) + 1;
					  int	next_y 	= (int)((seg[1] + (seg[2]-seg[1])/2)*pos_ratio) + 1;
					  
					  gc.drawLine(
							y_axis_bottom_x + prev_x,
							y_axis_bottom_y - prev_y,
							y_axis_bottom_x + next_x,
							y_axis_bottom_y - next_y );
														
					  prev_x = next_x;
					  prev_y = next_y;
				  }
			  }
			  
			  gc.setForeground( Colors.black );
		  }
		  
		  SpeedManagerLimitEstimate[] bad_down = mapper.getBadDownloadHistory();
		  
		  if ( bad_down.length > 0 ){
			  
			  gc.setForeground( Colors.red );

			  gc.setLineWidth( 3 );
			  
			  for (int i=0;i<bad_down.length;i++){
				  
				  int speed = bad_down[i].getBytesPerSec();
				  
				  int	y = max_y==0?0:( speed * usable_height / max_y );
			  
				  gc.drawLine(
						  	y_axis_bottom_x + 0,
						  	y_axis_bottom_y - y,
							y_axis_bottom_x + 10,
							y_axis_bottom_y - y );
			  }
			  
			  gc.setForeground( Colors.black );
			  
			  gc.setLineWidth( 1 );
		  }
		  
		  String	y_text = labels[1] + " - " + formatters[1].format( max_y+1 );

		  gc.drawText( y_text, y_axis_top_x+4, y_axis_top_y + 2, SWT.DRAW_TRANSPARENT );

		  gc.drawText( title, ( bounds.width - title.length()*char_width )/2, 1, SWT.DRAW_TRANSPARENT );
		  
		  gc.dispose();

		  return( image );
	  }
	  
	  protected void
	  dispose()
	  {
			if ( buffer_image != null && ! buffer_image.isDisposed()){
				
				buffer_image.dispose();
			}
			
			COConfigurationManager.removeParameterListener("Graphics Update",this);
	  }
  }
  
  public static class
  limitToTextHelper
  {
	  String	msg_text_unknown;
	  String	msg_text_estimate;
	  String	msg_text_choke_estimate;
	  String	msg_text_measured_min;
	  String	msg_text_measured;
	  String	msg_text_manual;
	  String	msg_unlimited;
	  
	  String[]	setable_types;
	  
	  public
	  limitToTextHelper()
	  {
		  msg_text_unknown			= MessageText.getString( "SpeedView.stats.unknown" );
		  msg_text_estimate			= MessageText.getString( "SpeedView.stats.estimate" );
		  msg_text_choke_estimate	= MessageText.getString( "SpeedView.stats.estimatechoke" );
		  msg_text_measured			= MessageText.getString( "SpeedView.stats.measured" );
		  msg_text_measured_min		= MessageText.getString( "SpeedView.stats.measuredmin" );
		  msg_text_manual			= MessageText.getString( "SpeedView.stats.manual" );
		  
		  msg_unlimited			= MessageText.getString( "ConfigView.unlimited" );
		  
		  setable_types =  new String[]{ "", msg_text_estimate, msg_text_measured, msg_text_manual };
	  }
	  
	  public String[]
	  getSettableTypes()
	  {
		  return( setable_types );
	  }
	  
	  public String
	  getSettableType(
		  SpeedManagerLimitEstimate limit )
	  {
		  float type = limit.getEstimateType();

		  String	text;
		  
		  if ( type == SpeedManagerLimitEstimate.TYPE_UNKNOWN){

			  text = "";
			  
		  }else if ( type == SpeedManagerLimitEstimate.TYPE_MANUAL){

			  text = msg_text_manual;

		  }else if ( type == SpeedManagerLimitEstimate.TYPE_MEASURED){

			  text = msg_text_measured;

		  }else if ( type == SpeedManagerLimitEstimate.TYPE_MEASURED_MIN){

			  text = msg_text_measured;

		  }else if ( type == SpeedManagerLimitEstimate.TYPE_CHOKE_ESTIMATED){

			  text = msg_text_estimate;

		  }else{

			  text = msg_text_estimate;
		  }
		  
		  return( text );  	  }
	  
	  public String
	  typeToText(
		float 	type )
	  {
		  String	text;
		  
		  if ( type == SpeedManagerLimitEstimate.TYPE_UNKNOWN){

			  text = msg_text_unknown;
			  
		  }else if ( type == SpeedManagerLimitEstimate.TYPE_MANUAL){

			  text = msg_text_manual;

		  }else if ( type == SpeedManagerLimitEstimate.TYPE_MEASURED){

			  text = msg_text_measured;

		  }else if ( type == SpeedManagerLimitEstimate.TYPE_MEASURED_MIN){

			  text = msg_text_measured_min;

		  }else if ( type == SpeedManagerLimitEstimate.TYPE_CHOKE_ESTIMATED){

			  text = msg_text_choke_estimate;

		  }else{

			  text = msg_text_estimate;
		  }
		  
		  return( text );  
	  }
	  
	  public float
	  textToType(
		String	text )
	  {
		  if ( text.equals( msg_text_estimate )){
			  
			  return( SpeedManagerLimitEstimate.TYPE_ESTIMATED);
			  
		  }else if ( text.equals( msg_text_choke_estimate )){
			  
			  return( SpeedManagerLimitEstimate.TYPE_CHOKE_ESTIMATED);
			  
		  }else if ( text.equals( msg_text_measured )){
			  
			  return( SpeedManagerLimitEstimate.TYPE_MEASURED);
			  
		  }else if ( text.equals( msg_text_manual )){
			  
			  return( SpeedManagerLimitEstimate.TYPE_MANUAL);
			  
		  }else{
			  
			  return( SpeedManagerLimitEstimate.TYPE_UNKNOWN);
		  }
	  }
	  
	  public String
	  getLimitText(
		 SpeedManagerLimitEstimate	limit )
	  {
		  float type = limit.getEstimateType();
		  
		  String	text = typeToText( type );
		  
		  int	l = limit.getBytesPerSec();
		  
		  if ( l == 0 ){
			  
			  return( msg_unlimited + " (" + text + ")");
			  
		  }else{
		  
			  return( DisplayFormatters.formatByteCountToKiBEtcPerSec( l ) + " (" + text + ")");
		  }
	  }
	  
	  public String
	  getUnlimited()
	  {
		  return( msg_unlimited );
	  }
  }

	public boolean eventOccurred(UISWTViewEvent event) {
    switch (event.getType()) {
      case UISWTViewEvent.TYPE_CREATE:
      	swtView = (UISWTView)event.getData();
      	swtView.setTitle(MessageText.getString(MSGID_PREFIX + ".title.full"));
        break;

      case UISWTViewEvent.TYPE_DESTROY:
        delete();
        break;

      case UISWTViewEvent.TYPE_INITIALIZE:
        initialize((Composite)event.getData());
        break;

      case UISWTViewEvent.TYPE_LANGUAGEUPDATE:
      	Messages.updateLanguageForControl(getComposite());
        break;

      case UISWTViewEvent.TYPE_DATASOURCE_CHANGED:
        break;
        
      case UISWTViewEvent.TYPE_FOCUSGAINED:
      	break;
        
      case UISWTViewEvent.TYPE_REFRESH:
        refresh();
        break;
        
      case StatsView.EVENT_PERIODIC_UPDATE:
      	periodicUpdate();
      	break;
    }

    return true;
  }
}

