package;
import bluntirc.*;
import irc.*;

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.event.TableModelEvent;
import javax.swing.table.*;
import java.awt.datatransfer.*;
import java.util.logging.Logger;

abstract class ChnnelInfoComparator implements Comparator{
	int type;
	public ChnnelInfoComparator(int type){this.type=type;}
	public boolean equals(Object obj){ 
		if(obj instanceof ChnnelInfoComparator) return ((ChnnelInfoComparator)obj).type==type;
		return false;
	}
}
class ChannelInfo{
	int users;
	int flow;
	int lastpos;
	byte[] rawbytes;
	String shortname;
	String sortname;
	String topic;
	public ChannelInfo(int flow,int users,byte[] name,String topic){
		this.flow=flow;
		this.users=users;
		this.rawbytes=name;
		this.topic=topic;
		shortname = sortname = base.Util.fromJIS(rawbytes);
		if(shortname.length()>0){
			if(shortname.charAt(0)=='!' && shortname.length()>=6) shortname="!"+shortname.substring(6);
			sortname = shortname.substring(1);
		}
	}


	public static Comparator createComparator(int type){
		if(type==3) return new ChnnelInfoComparator(type){
			public int compare(Object o1,Object o2){
				int i = ((ChannelInfo)o1).topic.compareToIgnoreCase(((ChannelInfo)o2).topic);
				return i!=0?i: ((ChannelInfo)o1).lastpos - ((ChannelInfo)o2).lastpos;
			}
		};
		if(type==2) return new ChnnelInfoComparator(type){
			public int compare(Object o1,Object o2){
				int i = ((ChannelInfo)o1).sortname.compareToIgnoreCase(((ChannelInfo)o2).sortname);
				return i!=0?i: ((ChannelInfo)o1).lastpos - ((ChannelInfo)o2).lastpos;
			}
		};
		if(type==1) return new ChnnelInfoComparator(type){
			public int compare(Object o1,Object o2){
				int i= ((ChannelInfo)o1).users - ((ChannelInfo)o2).users;
				return i!=0?i: (((ChannelInfo)o1).lastpos - ((ChannelInfo)o2).lastpos);
			}
		};
		if(type==0) return new ChnnelInfoComparator(type){
			public int compare(Object o1,Object o2){
				int i= ((ChannelInfo)o1).flow - ((ChannelInfo)o2).flow;
				return i!=0?i:(((ChannelInfo)o1).lastpos - ((ChannelInfo)o2).lastpos);
			}
		};
		return null;
	}
};

abstract class  TableView implements TableModel{
	abstract public void fireTableModelListener(TableModelEvent e);
};

class ChannelListDoc{
	ChannelListDoc(CTN_Conn conn){
		this.conn=conn;
	}
	CTN_Conn conn;
	void double_click(int row,int col){
		ChannelInfo ci = getByRow(row);
		Vector v = new Vector();
		v.add("join");
		v.add(ci.rawbytes);
		conn.conn.SendToServer(v);
	}

	TableView  view;
	public void removeView	(TableView  v){ view=null; }
	public void addView		(TableView  v){ view=v;}

	LinkedList s_ary =new LinkedList();

	public int getRowCount(){ return s_ary.size(); }
	public ChannelInfo getByRow(int row){ return (ChannelInfo)s_ary.get(row);}
	public int findByName(byte[] key){
		int i=0;
		for(Iterator it=s_ary.iterator();it.hasNext();++i){
			ChannelInfo m=(ChannelInfo)it.next();
			if(Arrays.equals(m.rawbytes,key)) return i;
		}
		return -1;
	}

	//////////////////////////////////////////////////////
	// 比較器
	Comparator comparator=ChannelInfo.createComparator(0);

	// ChannelInfoを見て挿入する位置を返す
	public int findInsertPoint(ChannelInfo key){
		int i=0;
		for(Iterator it=s_ary.iterator();it.hasNext();++i){
			ChannelInfo m=(ChannelInfo)it.next();
			if(comparator.compare(m,key)>=0) return i;
		}
		return i;
	}

	// ソート
	public void sort(Comparator new_comparator){
		if(new_comparator!=null) comparator= new_comparator;
		int i=0;
		for(Iterator it=s_ary.iterator();it.hasNext();){ ((ChannelInfo)(it.next())).lastpos=++i;}
		Collections.sort(s_ary,comparator);
		if(view!=null){view.fireTableModelListener(new TableModelEvent(view));}
	}

	//////////////////////////////////////////////////////
	// ユーザリストの変化に合わせて呼ばれて、
	// リストを変更してview にイベントを通知する

/*
	public void setWhoReply(IRCUser who){
		int i=findByName( who.getNickBytes() );
		if(i!=-1 && view!=null) view.fireTableModelListener(new TableModelEvent(view, i, i, TableModelEvent.ALL_COLUMNS, TableModelEvent.UPDATE));
	}
*/
	public void RemoveChannelInfo(ChannelInfo who){
		int i= findByName(who.rawbytes);
		if(i==-1) return;
		s_ary.remove(i);
		if(view!=null){view.fireTableModelListener(new TableModelEvent(view, i, i, TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE));}
	}
	public void clear(){
		int size= s_ary.size();
		if(size==0) return;
		s_ary.clear();
		if(view!=null){view.fireTableModelListener(new TableModelEvent(view, 0, size-1, TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE));}
	}
	
	public void AddChannelInfo( ChannelInfo who ){ ChangeChannelInfo(who); }
	public void ChangeChannelInfo(ChannelInfo who){
		int i=findByName(who.rawbytes);
		// リスト中での位置が変わることがあるので、古い項目を一度リストから取り除く。
		if(i!=-1){
			s_ary.remove(i);
			if(view!=null) view.fireTableModelListener(new TableModelEvent(view, i, i, TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE));
		}
		// 追加する
		i = findInsertPoint( who );
		s_ary.add(i,who);
		if(view!=null) view.fireTableModelListener(new TableModelEvent(view, i, i, TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT));
	}
};

/////////////////////////////////////////////////////////////
// テーブルのヘッダのセルを描画するコンポーネントを返す

class FontInfo{
	Color bg,bg2;
	Color fg,fg2;
	Font font;
	FontInfo(Color fg,Color bg,Color fg2,Color bg2,Font font){
		this.fg=fg;
		this.bg=bg;
		this.fg2=fg2;
		this.bg2=bg2;
		this.font=font;
	}
}

class MyTableHeaderRenderer
implements TableCellRenderer
{
	FontInfo cf = new FontInfo(Color.black,Color.white,Color.white,Color.black,new Font(null,Font.PLAIN,12));
	TableCellRenderer orig;
	Insets inset = new Insets(0,0,0,0);

	public MyTableHeaderRenderer(TableCellRenderer orig)
	{ this.orig=orig; }

	public Component getTableCellRendererComponent
	(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
	{
		/*
			ヘッダのデフォルトのレンダラーは
			DefaultTableCellRendererを軽く拡張して
			setBorder(UIManager.getBorder("TableHeader.cellBorder"));
			しただけのものだ。
			DefaultTableCellRenderer はJLabelを継承している。
		*/
		Component component = orig.getTableCellRendererComponent
			(table,value,isSelected,hasFocus,row,column);

		// スタイルを変更する
		if(cf!=null){
			component.setFont(cf.font);
			component.setForeground(cf.fg);
			if(component instanceof JLabel)
				((JLabel)component).setHorizontalAlignment(SwingConstants.LEADING);
			if(cf.bg==null){
				if(component instanceof JComponent)
					((JComponent)component).setOpaque(false);
			}else{
				component.setBackground(cf.bg);
			}
		}
		return component;
	}
}

class UserTableCellRenderer
extends JComponent
implements TableCellRenderer 
{
	// セルの情報
	Object value;
	boolean isSelected;
	boolean hasFocus ;
	int col; // モデル桁
	FontInfo cf = new FontInfo(Color.black,Color.white,Color.white,Color.green,new Font(null,Font.PLAIN,12));
	public Component getTableCellRendererComponent
	(JTable table,Object value,boolean isSelected,boolean hasFocus,int row,int column)
	{
		this.isSelected= isSelected;
		this.hasFocus  = hasFocus;
		this.col       = table.convertColumnIndexToModel(column);
		this.value     = value;
		return this;
	}

	public void update(Graphics g){ paint(g); }
	public void paint(Graphics g){
		int width  = getWidth();
		int height = getHeight();
		if(isSelected){
			g.setColor(cf.bg2);
			g.fillRect(1,1,width-2,height-2);
		}
		if(hasFocus){
			g.setColor(cf.bg);
			g.drawRect(0,0,width-1,height-1);
		}

		String text =value.toString();

		g.setFont(cf.font);
		FontMetrics fm = g.getFontMetrics();
		int text_width = fm.stringWidth(text);

		g.setColor(isSelected?cf.fg2:cf.fg);
		int align=0;
		switch(col){
		case 0: align=1; break;
		case 1: align=1; break;
		case 2: align=-1; break;
		case 3: align=-1; break;
		}
		boolean right=false;
		if(align==1){
			right=true;
		}else if( align==0){
			// はみだす時だけ右揃え
			if( text_width > width-4) right=true;
		}
		g.drawString(text
			,(right?(width-2-text_width):2)
			,fm.getHeight()-fm.getDescent()
		);
	}
}

class ChannelListView extends TableView
 //,java.awt.datatransfer.Transferable
{
	public ChannelListView(boolean trans){
		addComponents(trans);
		table.addMouseListener(new MouseListener(){
			public void mouseEntered (MouseEvent e){}
			public void mouseExited  (MouseEvent e){}
			public void mousePressed (MouseEvent e){CheckPopup(e);}
			public void mouseReleased(MouseEvent e){CheckPopup(e);}
			public void mouseClicked(MouseEvent e){
				if(0!=(e.getModifiers() & MouseEvent.BUTTON1_MASK  )
				&& e.getClickCount()==2
				) CheckDoubleClick(e);
			}
		});
		table.getTableHeader().addMouseListener(new MouseAdapter(){
			public void mouseClicked(MouseEvent e){
				int col = table.convertColumnIndexToModel(table.getColumnModel().getColumnIndexAtX(e.getX()));
				if( col==-1) return;
                if( e.getClickCount()==1 ) sort(col);
			}
		});

		row_selection = table.getSelectionModel();
		col_selection = table.getColumnModel().getSelectionModel() ;
        table.setColumnSelectionAllowed(true); 
//        table.setTransferHandler(new MyTransferHandler(this));
		table.setDragEnabled(true);
	}

	// カラムヘッダを押してソートする
	int last_col=1;
	boolean last_ascending=true;
	class ReverceComparator implements Comparator{
		Comparator src;
		ReverceComparator(Comparator src){this.src=src;}
		public int compare(Object o1,Object o2){ return - src.compare(o1,o2);}
	};
	public void sort(int col){
		if(last_col==col){
			last_ascending=!last_ascending;
		}else{
			last_ascending=true;
		}
		last_col=col;
		if(doc!=null) doc.sort(last_ascending?comparators[last_col]:new ReverceComparator(comparators[last_col]));
	}

	/////////////////////////////////////////////////////////
	// implements TableModel
	LinkedList listener = new LinkedList();
	public void addTableModelListener(TableModelListener l){ if(-1==listener.indexOf(l)) listener.add(l); }
	public void removeTableModelListener(TableModelListener l){ listener.remove(l); }
	public void fireTableModelListener(TableModelEvent e){
		for(Iterator i=listener.iterator();i.hasNext();)
		{ ((TableModelListener)i.next()).tableChanged(e); }
	}

	/////////////////////////////////////////////////////////
	static String[] header=new String[]{
		"flow",
		"users",
		"name",
		"topic",
	};
	static int[] pwidth = { 3,3,20,80};
	public int getColumnCount(){ return header.length; }
	public String getColumnName(int index){ return header[index];}
	public Class getColumnClass(int index){ return String.class;}

	public int getColumnPreferredWidth(int index){
		FontMetrics fm=table.getFontMetrics(table.getFont());
		return 12+fm.stringWidth("W")*pwidth[index];
	}

	// 実際のデータはチャンネルごとに用意されるChannelListDocが持っている。
	ChannelListDoc doc;
	public void setDocument(ChannelListDoc new_doc){
		if(doc==new_doc) return;
		if(doc!=null) doc.removeView(this);
		doc=new_doc;
		if(doc!=null) doc.addView(this);
		fireTableModelListener(new TableModelEvent(this));
	}
	public int    getRowCount(){ return doc==null?0:doc.getRowCount();}
	public boolean isCellEditable(int rowIndex,int columnIndex){ return false; }
	public void   setValueAt(Object aValue,int rowIndex,int columnIndex){}

	////////////////////////////////////////////////////////////
	// セルの桁ごとの処理

	public Object getValueAt(int rowIndex,int columnIndex){
		if(doc==null) return null;
		ChannelInfo u = doc.getByRow(rowIndex);
		if(u==null) return "no item";
		switch(columnIndex){
		case 0: return ""+u.flow;
		case 1: return ""+u.users;
		case 2: return u.shortname;
		case 3: return u.topic;
		}
		return "??";
	}

	static Comparator no_comparator = new Comparator(){ public int compare(Object o1,Object o2){return 0;}};
	Comparator[] comparators=new Comparator[]{
		ChannelInfo.createComparator(0),
		ChannelInfo.createComparator(1),
		ChannelInfo.createComparator(2),
		ChannelInfo.createComparator(3),
	};

	// ダブルクリック
	void CheckDoubleClick(MouseEvent e){
		ListSelectionModel lsm = row_selection;
		// ダブルクリックの際は anchor しか見ない
		if(lsm.isSelectionEmpty()) return;
		int row= lsm.getAnchorSelectionIndex();
		int col= table.convertColumnIndexToModel(col_selection.getAnchorSelectionIndex());
		if(row!=-1 && col!=-1 && doc!=null){
			doc.double_click(row,col);
		}
	}

	// 右クリックメニュー
	void CheckPopup(MouseEvent e){
		if(e.isPopupTrigger()){
			int row = table.rowAtPoint(new Point(e.getX(),e.getY()));
			int col = table.getColumnModel().getColumnIndexAtX(e.getX());
			if(row<0 || col<0){
				// どこでもない場所をクリックしたのなら、選択範囲をクリアする
				table.clearSelection();
			}else if(! table.isCellSelected(row,col) ){
				// 選択範囲の中をクリックしたのでないなら、
				// 選択範囲を変更する
				table.clearSelection();
				row_selection.setAnchorSelectionIndex(row);
				row_selection.addSelectionInterval(row,row);
				col_selection.setAnchorSelectionIndex(col);
				col_selection.addSelectionInterval(col,col);
				table.repaint();
			}
			Logger.global.info("popup-menu");
		}
	}

	/////////////////////////////////////////////////////////
	// GUI部品
	public JTable table;
	JScrollPane scroll;
	UserTableCellRenderer cell_renderer;
	MyTableHeaderRenderer cell_renderer2;

	// 部品の初期化
	public void addComponents(boolean trans){
		table = new JTable(this); // ,trans);
		cell_renderer =new UserTableCellRenderer();
		table.setDefaultRenderer(String.class,cell_renderer);
		cell_renderer2=new MyTableHeaderRenderer(table.getTableHeader().getDefaultRenderer());
		table.getTableHeader().setDefaultRenderer(cell_renderer2);

		table.setShowVerticalLines(false);
	    table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		table.setCellSelectionEnabled(true);
		table.setRowSelectionAllowed(true);
		table.setColumnSelectionAllowed(true);

		TableColumnModel tcm =table.getColumnModel() ;
		for(int i=0;i<tcm.getColumnCount();++i){
			int width = getColumnPreferredWidth(i);
			tcm.getColumn(i).setPreferredWidth(width);
		}
		scroll = new JScrollPane(table);
	//    scroll.setPreferredSize(new Dimension(60, 120));

		if(trans){
			// JTableはscrollのColumnHeaderを設定する
			// table.configureEnclosingScrollPane();
			scroll.getColumnHeader().setOpaque(false);
		}
		scroll.setBorder(null);
		scroll.setOpaque(false);
		scroll.getViewport().setOpaque(false);
	}
	public JComponent getComponent(){ return scroll; }

/*
	void reloadFontInfo(){
		cell_renderer .cf=App.style_manager.findFontInfo("UserTable");
		cell_renderer2.cf=App.style_manager.findFontInfo("UserTableHeader");
		table.setGridColor( App.style_manager.findFontInfo("UserTableGrid").fg);
		int h = table.getFontMetrics(cell_renderer.cf.font).getHeight()+2;
		if(h<16)h=16;
		table.setRowHeight( h );
	}
*/
	/////////////////////////////////////////////////////////
	// 選択範囲

	ListSelectionModel row_selection;
	ListSelectionModel col_selection;

	public boolean isSelectedOne(){
		ListSelectionModel lsm = row_selection;
		// ダブルクリックの際はanchor しか見ない
		if(lsm.isSelectionEmpty()) return false;
		int row= lsm.getAnchorSelectionIndex();
		int col= col_selection.getAnchorSelectionIndex();
		return (row!=-1 && col!=-1 );
	}

	public boolean isSelectedMulti(){
		ListSelectionModel lsm = row_selection;
		if(lsm.isSelectionEmpty()) return false;
		return true;
	}
/*
	public ChannelInfo getSelectedOne(){
		if(doc!=null){
			ListSelectionModel lsm = row_selection;
			if(!lsm.isSelectionEmpty()){
				int row= lsm.getAnchorSelectionIndex();
				if(row!=-1)return doc.getByRow(row);
			}
		}
		return null;
	}
	public LinkedList  getSelectedMulti(){
		if(doc!=null){
			ListSelectionModel lsm = row_selection;
			if(!lsm.isSelectionEmpty()){
				int start = lsm.getMinSelectionIndex();
				int end   = lsm.getMaxSelectionIndex();
				if(start>=0){
					LinkedList  v= new LinkedList ();
					for(int i=0;i<=end;++i){ //endを含むことに注意
						if(lsm.isSelectedIndex(i)){
							v.add(doc.getByRow(i));
						}
					}
					return v;
				}
			}
		}
		return null;
	}
*/
}


class ChannelListWindow extends JFrame{
	ChannelListView view = new ChannelListView(false);
	ChannelListDoc doc;
	JPanel btn_panel = new JPanel();
	JButton btn_update;
	JTextField count_tf =new JTextField();
	int count =0;
	ChannelListWindow(CTN_Conn conn,String title){
		super(title);
		this.conn=conn;
		doc = new ChannelListDoc(conn);
		setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
		view.setDocument(doc);

		AbstractAction[] btns= {
			new AbstractAction(){
				String[] texts ={
					Action.NAME,"更新",
					Action.LONG_DESCRIPTION,  "リストを更新します",
					Action.SHORT_DESCRIPTION ,"リストを更新します",
				};
				public Object getValue(String key){ for(int i=0;i<texts.length;i+=2) if(texts[i].equals(key)) return texts[i+1]; return super.getValue(key);}
				public void actionPerformed(ActionEvent e){ start_update(); }
			},
		};
		btn_update = new JButton("更新");
		btn_update.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e){ 
				Object o = e.getSource();
				while(o!=null){
					if(o instanceof  ChannelListWindow){
						((ChannelListWindow)o).list();
						return;
					}
					if(o instanceof java.awt.Component){
						java.awt.Container c = ((java.awt.Component)o).getParent();
						o=c;
						continue;
					}
					break;
				}
			}
		});
		count_tf.setEditable(false);
		btn_panel.add( btn_update);
		btn_panel.add( count_tf);
		btn_panel.add( new JLabel("個") );
		BorderLayout bl = new BorderLayout();
		getContentPane().setLayout(bl);
		getContentPane().add(bl.CENTER,view.getComponent());
		getContentPane().add(bl.SOUTH,btn_panel);
        pack();
	}
	CTN_Conn conn;
	public void list(){
		doc.clear();
		count=-1;inc();
		conn.cmd_list();
		btn_update.setEnabled(false);
		btn_update.setText("更新中");
	}
	public void inc(){
		++count;
		count_tf.setText(""+count);
	}
};

class Plugin
implements hook.IRCMessageRewrite1
{
	public Plugin(ScriptManager manager){}
	public static final String keyname="ChannelListDialog.java";
	public void unload(){
		for(Iterator it = create_window_list.iterator();it.hasNext();){
			CTN_Conn conn = (CTN_Conn)it.next();
			ChannelListWindow clw = (ChannelListWindow)conn.property.get(keyname);
			conn.property.put(keyname,"unload");
			clw.dispose();
		}
	}

	LinkedList create_window_list = new LinkedList();
	ChannelListWindow findChannelListWindow(CTN_Conn conn){
		ChannelListWindow clw;
		Object o = conn.property.get(keyname);
		if(o instanceof ChannelListWindow){
			clw = (ChannelListWindow)o;
		}else{
			clw = new ChannelListWindow(conn,conn.GetCaption());
			create_window_list.add(conn);
			conn.property.put(keyname,clw);
		}
		clw.setVisible(true);
		return clw;
	}
	public IRCMessage rewriteIRCMessage1(IRCMessage m){
		if( m.cmd.equals("322") ){
			ChannelListWindow clw = findChannelListWindow((CTN_Conn)m.conn.Extra);
			int size = m.size();
			clw.inc();
			clw.doc.AddChannelInfo(new ChannelInfo(
				-1
				,(size>2?Integer.parseInt( base.Util.fromJIS(m.getParam(2))) :-1)
				,(size>1?m.getParam(1):"?".getBytes())
				,(size>3?base.Util.fromJIS(m.getParam(3)):"?")
			));
			return null;
		}
		if( m.cmd.equals("328") ){
			ChannelListWindow clw = findChannelListWindow((CTN_Conn)m.conn.Extra);
			int size = m.size();
			clw.inc();
			clw.doc.AddChannelInfo(new ChannelInfo(
				 (size>3?Integer.parseInt( base.Util.fromJIS(m.getParam(3))) :-1)
				,(size>2?Integer.parseInt( base.Util.fromJIS(m.getParam(2))) :-1)
				,(size>1?m.getParam(1):"?".getBytes())
				,(size>5?base.Util.fromJIS(m.getParam(5)):"?")
			));
			return null;
		}
		if(m.cmd.equals("323")){
			ChannelListWindow clw = findChannelListWindow((CTN_Conn)m.conn.Extra);
			clw.btn_update.setEnabled(true);
			clw.btn_update.setText("更新");
			return null;
		}
		return m;
	}
}
