/*
 * File    : CategoryImpl.java
 * Created : 09 feb. 2004
 * By      : TuxPaper
 *
 * Azureus - a Java Bittorrent client
 *
 * 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 ( see the LICENSE file ).
 *
 * 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
 */

package org.gudy.azureus2.core3.category.impl;

import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import org.gudy.azureus2.core3.category.Category;
import org.gudy.azureus2.core3.category.CategoryListener;
import org.gudy.azureus2.core3.download.*;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.IndentWriter;
import org.gudy.azureus2.core3.util.ListenerManager;
import org.gudy.azureus2.core3.util.ListenerManagerDispatcher;

import com.aelitis.azureus.core.AzureusCore;
import com.aelitis.azureus.core.AzureusCoreFactory;
import com.aelitis.azureus.core.networkmanager.LimitedRateGroup;
import com.aelitis.azureus.core.tag.TagDownload;
import com.aelitis.azureus.core.tag.TagFeatureRunState;
import com.aelitis.azureus.core.tag.Taggable;
import com.aelitis.azureus.core.tag.impl.TagBase;
import com.aelitis.azureus.core.util.IdentityHashSet;

public class 
CategoryImpl 
	extends TagBase 
	implements Category, Comparable, TagDownload 
{
  private String sName;
  private int type;
  private List<DownloadManager> managers = new ArrayList<DownloadManager>();

  private int upload_speed;
  private int download_speed;

  private Object UPLOAD_PRIORITY_KEY = new Object();
  
  private final Map<String,String>	attributes;
  
  private static AtomicInteger	tag_ids = new AtomicInteger();
  
  private LimitedRateGroup upload_limiter = 
	  new LimitedRateGroup()
	  {
		  public String 
		  getName() 
		  {
			  return( "cat_up: " + sName);
		  }
		  public int 
		  getRateLimitBytesPerSecond()
		  {
			  return( upload_speed );
		  }
		  
		  public void
		  updateBytesUsed(
				int	used )
		  {
			  
		  }
	  };
   
  private LimitedRateGroup download_limiter = 
	  new LimitedRateGroup()
  {
	  public String 
	  getName() 
	  {
		  return( "cat_down: " + sName);
	  }
	  public int 
	  getRateLimitBytesPerSecond()
	  {
		  return( download_speed );
	  }
	  
	  public void
	  updateBytesUsed(
			int	used )
	  {
		  
	  }
  };  
  
  private boolean destroyed;
  
  private static final int LDT_CATEGORY_DMADDED     = 1;
  private static final int LDT_CATEGORY_DMREMOVED   = 2;
	private ListenerManager<CategoryListener>	category_listeners = ListenerManager.createManager(
		"CatListenDispatcher",
		new ListenerManagerDispatcher<CategoryListener>()
		{
			public void
			dispatch(
				CategoryListener		target,
				int						type,
				Object					value )
			{
				if ( type == LDT_CATEGORY_DMADDED )
					target.downloadManagerAdded((Category) CategoryImpl.this, (DownloadManager)value);
				else if ( type == LDT_CATEGORY_DMREMOVED )
					target.downloadManagerRemoved(CategoryImpl.this, (DownloadManager)value);
			}
		});

  public CategoryImpl(CategoryManagerImpl manager, String sName, int maxup, int maxdown, Map<String,String> _attributes ) {
	super( manager, tag_ids.incrementAndGet(), sName );
	addTag();
	
    this.sName = sName;
    this.type = Category.TYPE_USER;
    upload_speed	= maxup;
    download_speed	= maxdown;
    attributes = _attributes;
  }

  public CategoryImpl(CategoryManagerImpl manager, String sName, int type, Map<String,String> _attributes) {
	super( manager, tag_ids.incrementAndGet(), sName);
	addTag();
	
    this.sName = sName;
    this.type = type;
    attributes = _attributes;
  }

  public void addCategoryListener(CategoryListener l) {
	  category_listeners.addListener( l );
  }

  public void removeCategoryListener(CategoryListener l) {
	  category_listeners.removeListener( l );
  }

  public boolean
  hasCategoryListener(
	 CategoryListener	l )
  {
	  return( category_listeners.hasListener( l ));
  }
  
  public String getName() {
    return sName;
  }
  
  public int getType() {
    return type;
  }
  
  public List<DownloadManager> getDownloadManagers(List<DownloadManager> all_dms) {
	  if ( type == Category.TYPE_USER ){
		  return managers;
	  }else if ( type == Category.TYPE_ALL || all_dms == null ){
		  return all_dms;
	  }else{
		  List<DownloadManager> result = new ArrayList<DownloadManager>();
		  for (int i=0;i<all_dms.size();i++){
			  DownloadManager dm = all_dms.get(i);
			  Category cat = dm.getDownloadState().getCategory();
			  if ( cat == null || cat.getType() == Category.TYPE_UNCATEGORIZED){
				  result.add( dm );
			  }
		  }
		  
		  return( result );
	  }
  }
  
  public void addManager(DownloadManagerState manager_state) {
  	Category manager_cat = manager_state.getCategory();
		if ((type != Category.TYPE_UNCATEGORIZED && manager_cat != this)
				|| (type == Category.TYPE_UNCATEGORIZED && manager_cat != null)) {
    	manager_state.setCategory(this);
      // we will be called again by CategoryManager.categoryChange
      return;
    }
    
    DownloadManager	manager = manager_state.getDownloadManager();
    
    	// can be null if called during downloadmanagerstate construction
    if ( manager == null ){
    	return;
    }
    
    addTaggable( manager );
    
    if (!managers.contains(manager)) {
    	if (type == Category.TYPE_USER) {
    		managers.add(manager);
    	}
      
      manager.addRateLimiter( upload_limiter, true );
      manager.addRateLimiter( download_limiter, false );
      
      int pri = getIntAttribute( AT_UPLOAD_PRIORITY, -1 );
      
      if ( pri > 0 ){
    	  
    	  	// another call-during-construction issue to avoid here
    	  
    	  if ( manager.getDownloadState() != null ){
    	  
    		  manager.updateAutoUploadPriority( UPLOAD_PRIORITY_KEY, true );
    	  }
      }
      
      category_listeners.dispatch(LDT_CATEGORY_DMADDED, manager);
    }
  }

  public void removeManager(DownloadManagerState manager_state) {
    if (manager_state.getCategory() == this) {
    	manager_state.setCategory(null);
      // we will be called again by CategoryManager.categoryChange
      return;
    }
    DownloadManager	manager = manager_state.getDownloadManager();

   	// can be null if called during downloadmanagerstate construction
    if ( manager == null ){
    	return;
    }
    
    removeTaggable( manager );
    
    if (type != Category.TYPE_USER || managers.contains(manager)) {
      managers.remove(manager);
      
      manager.removeRateLimiter( upload_limiter, true );
      manager.removeRateLimiter( download_limiter, false );
 
      int pri = getIntAttribute( AT_UPLOAD_PRIORITY, -1 );
      
      if ( pri > 0 ){
    	  
    	  	// another call-during-construction issue to avoid here
    	  
    	  if ( manager.getDownloadState() != null ){
    	  
    		  manager.updateAutoUploadPriority( UPLOAD_PRIORITY_KEY, false );
    	  }
      }
      
      category_listeners.dispatch( LDT_CATEGORY_DMREMOVED, manager );
    }
  }

  public void
  setDownloadSpeed(
	int		speed )
  {
	  if ( download_speed != speed ){
		  
		  download_speed = speed;
		  
		  CategoryManagerImpl.getInstance().saveCategories(this);
	  }
  }
  
  public int
  getDownloadSpeed()
  {
	  return( download_speed );
  }
  
  public void
  setUploadSpeed(
	int		speed )
  {
	  if ( upload_speed != speed ){
		  
		  upload_speed	= speed;
	  
		  CategoryManagerImpl.getInstance().saveCategories(this);
	  }
  }
  
  public int
  getUploadSpeed()
  {
	  return( upload_speed );
  }
  
  protected void
  setAttributes(
	Map<String,String> a )
  {
	  attributes.clear();
	  attributes.putAll( a );
  }
  
  protected Map<String,String>
  getAttributes()
  {
	  return( attributes );
  }
  
  public String
  getStringAttribute(
	String		name )
  {
	  return( attributes.get(name));
  }
  
  public void
  setStringAttribute(
	String		name,
	String		value )
  {
	  String old = attributes.put( name, value );
	  
	  if ( old == null || !old.equals( value )){
	  
		  CategoryManagerImpl.getInstance().saveCategories(this);
	  }

  }
  
  public int
  getIntAttribute(
	String		name )
  {
	  return( getIntAttribute( name, 0 ));
  }
  
  private int
  getIntAttribute(
	String		name,
	int			def )
  {
	 String str = getStringAttribute( name );
	 
	 if ( str == null ){
		 return( def );
	 }
	 return( Integer.parseInt( str ));
  }
  
  public void
  setIntAttribute(
	String		name,
	int			value )
  {
	  String	str_val = String.valueOf( value );
	  
	  String old = attributes.put( name, str_val );
	  
	  if ( old == null || !old.equals( str_val )){
	  
		  if ( name.equals( AT_UPLOAD_PRIORITY )){
			  
			  for ( DownloadManager dm: managers ){
				  
				  dm.updateAutoUploadPriority( UPLOAD_PRIORITY_KEY, value > 0 );
			  }
		  }
		  
		  CategoryManagerImpl.getInstance().saveCategories(this);
	  }

  }
  public boolean
  getBooleanAttribute(
	String		name )
  {
	 String str = getStringAttribute( name );
	 
	 return( str != null && str.equals( "true" ));
  }
  
  public void
  setBooleanAttribute(
	String		name,
	boolean		value )
  {
	  String str_val = value?"true":"false";
	  
	  String old = attributes.put( name, str_val );
	  
	  if ( old == null || !old.equals( str_val )){
	  
		  CategoryManagerImpl.getInstance().saveCategories(this);
	  }

  }
  
  public int 
  getTaggableTypes() 
  {
	  return( Taggable.TT_DOWNLOAD );
  }
  
  public String
  getTagName(
    boolean		localize )
  {
	  if ( localize ){
		  if ( type == Category.TYPE_ALL ||  type == Category.TYPE_UNCATEGORIZED){
			  return( MessageText.getString( getTagNameRaw()));
		  }
	  }
	  return( super.getTagName(localize));
  }
	
  public boolean
  supportsTagRates()
  {
	  return( false );
  }
	
  public boolean
  supportsTagUploadLimit()
  {
	  return( true );
  }

  public boolean
  supportsTagDownloadLimit()
  {
	  return( true );
  }
	
  public int
  getTagUploadLimit()
  {
	  return( getUploadSpeed());
  }

  public void
  setTagUploadLimit(
		  int		bps )
  {
	  setUploadSpeed( bps );
  }

  public int
  getTagCurrentUploadRate()
  {
	  return( -1 );
  }

  public int
  getTagDownloadLimit()
  {
	  return( getDownloadSpeed());
  }

  public void
  setTagDownloadLimit(
		  int		bps )
  {
	  setDownloadSpeed( bps );
  }

  public int
  getTagCurrentDownloadRate()
  {
	  return( -1 );
  }
  
  public int
  getTagUploadPriority()
  {
	  if ( type == Category.TYPE_USER ){
		  
		  return( getIntAttribute( AT_UPLOAD_PRIORITY ));
		  
	  }else{
		  
		  return( -1 );
	  }
  }

  public void
  setTagUploadPriority(
	  int		priority )
  {
	  setIntAttribute( AT_UPLOAD_PRIORITY, priority );
  }
  
  public boolean
  getCanBePublicDefault()
  {
	  return( type == Category.TYPE_USER );
  }
  
  public boolean
  supportsTagTranscode()
  {
	  return( false );
  }

  public String[]
  getTagTranscodeTarget()
  {
	  return( null );
  }

  public void
  setTagTranscodeTarget(
	  String		uid,
	  String		display_name )
  {
  }
	
  public Set<DownloadManager>
  getTaggedDownloads()
  {
  	AzureusCore core = AzureusCoreFactory.getSingleton();
  	if (!core.isStarted()) {
  		return new IdentityHashSet<DownloadManager>();
  	}
	 return( new IdentityHashSet<DownloadManager>(getDownloadManagers( core.getGlobalManager().getDownloadManagers())));
  }
  
  public Set<Taggable> 
  getTagged() 
  {
	  return((Set<Taggable>)(Object)( getTaggedDownloads()));
  }
  
	public int 
	getTaggedCount() 
	{
		return( getTagged().size());
	}
		
	public boolean 
	hasTaggable(
		Taggable	t )
	{
		return( getTagged().contains( t ));
	}
	
	public int
	getRunStateCapabilities()
	{
		return( TagFeatureRunState.RSC_NONE );
	}
	
	public boolean
	hasRunStateCapability(
		int		capability )
	{
		return( false );
	}
	
	public boolean[]
	getPerformableOperations(
   		int[]	ops )
	{
		return( new boolean[ ops.length]);
	}
	
	public void
	performOperation(
		int		op )
	{
		Debug.out( "derp" );
	}
	
  protected void
  destroy()
  {
	  if ( !destroyed ){
		  
		  destroyed = true;
	  
		  removeTag();
	  }
  }
  
  public int compareTo(Object b)
  {
    boolean aTypeIsUser = type == Category.TYPE_USER;
    boolean bTypeIsUser = ((Category)b).getType() == Category.TYPE_USER;
    if (aTypeIsUser == bTypeIsUser)
      return sName.compareToIgnoreCase(((Category)b).getName());
    if (aTypeIsUser)
      return 1;
    return -1;
  }
  
  public void dump(IndentWriter writer) {
	if ( upload_speed != 0 ){
		writer.println( "up=" + upload_speed );
	}
	if ( download_speed != 0 ){
		writer.println( "down=" + download_speed );
	}
	if ( attributes.size() > 0 ){
		
		writer.println( "attributes: " + attributes );
	}
	}
}
