// A.Krzysztof Kwasniewski's Cobweb Poset
// Applet draws multi-blocks of the form P_{k,n-k}
// of layer < Phi_1 --> Phi_N >
// for the Natural numbers, Fibonacci numbers and 
// Gaussian numbers
//
// author: Maciej Dziemianczuk
// www.faces-of-nature.art.pl/cobwebposet.html
//

import java.awt.*;
import java.awt.event.*;
import java.applet.*;


// -----------------------------------------------------------------------------
class CVertex
{
	// position on the screen
	int x;
	int y;
	
	static int RADIUS = 7;
	
	// ----------------------------------------------
	public CVertex(int _x, int _y)
	{
		x = _x;
		y = _y;
	}
	
	// ----------------------------------------------
	public void draw(Graphics g)
	{
		g.fillOval(x-RADIUS/2, y-RADIUS/2, RADIUS, RADIUS);
	}
	
	// ----------------------------------------------
	public void draw_chain(Graphics g, CVertex to)
	{
		g.drawLine(x, y, to.x, to.y);
	}
}

// -----------------------------------------------------------------------------
class CCobwebAdmissibleSeq
{
	
	// ----------------------------------------------
	public CCobwebAdmissibleSeq() { }
	
	// ----------------------------------------------
	public int F(int _n) { return 0; }
	public int LambdaM(int _m, int _k) { return 0; }
	public int LambdaK(int _m, int _k) { return 0; }
	
	// ----------------------------------------------
	// get combination n after k
	public long comb(int n, int k)
	{
		
		if (k > n) return 0;
		if (n == k) return F(1);
		if (k + 1 == n) return F(n);
		
		// numerator & dominator
		long numerator = 1;
		long denominator = 1;
		for (int i=0; i<k; ++i)
		{
			numerator *= F(n-i);
			denominator *= F(k-i);
		}
		
		return numerator / denominator;
	}
	
}

// -----------------------------------------------------------------------------
class CSeqNatural extends CCobwebAdmissibleSeq
{
	// ----------------------------------------------
	public CSeqNatural() { }
	// ----------------------------------------------
	public int F(int _n)
	{
		return _n;
	}	
	// ----------------------------------------------
	public int LambdaM(int _m, int _k)
	{
		return 1;
	}
	// ----------------------------------------------
	public int LambdaK(int _m, int _k)
	{
		return 1;
	}
}

// -----------------------------------------------------------------------------
class CSeqFibonacci extends CCobwebAdmissibleSeq
{
	// ----------------------------------------------
	public CSeqFibonacci() { }
	// ----------------------------------------------
	public int F(int _n)
	{
		if (_n < 1) return 0;
		if (_n == 1 || _n == 2) return 1;
		return F(_n-1) + F(_n-2);
	}
	// ----------------------------------------------
	public int LambdaM(int _m, int _k)
	{
		return F(_k-1);
	}
	// ----------------------------------------------
	public int LambdaK(int _m, int _k)
	{
		return F(_m+1);
	}
}

// -----------------------------------------------------------------------------
class CSeqGauss extends CCobwebAdmissibleSeq
{
	private int _q = 2;
	
	// ----------------------------------------------
	public CSeqGauss() { }
	// ----------------------------------------------
	public int F(int _n)
	{
		if (_n < 1) return 0;
    	if (_n == 1) return 1;
		
    	return F(1) * ((int)Math.pow(_q, _n) - 1)/(_q - 1);
    
	}
	// ----------------------------------------------
	public int LambdaM(int _m, int _k)
	{
		return (int)Math.pow(_q, _k);
	}
	// ----------------------------------------------
	public int LambdaK(int _m, int _k)
	{
		return 1;
	}
}




// -----------------------------------------------------------------------------
// Calculate partition i.e. set of blocks
//
// -----------------------------------------------------------------------------
class CGetPartition
{
	
	private int NumberOfBlocks;
	private int N; // size of layer i.e. <Phi_1 --> Phi_N>
	private int K; // number of levels in correct block
	private int M; // N - K
	
	// Sequence
	private CCobwebAdmissibleSeq Seq;
	
	// [x][][] - block
	// [][x][] - block's level 
	// [][][x] - level's element
	
	public int Partition[][][];
	
	// ----------------------------------------------
	public CGetPartition(int _n, int _k, CCobwebAdmissibleSeq _seq)
	{
		Seq = _seq;

		N = _n;
		K = _k;
		M = N - K;
		
		NumberOfBlocks = (int)Seq.comb(N, K);
		//System.out.println("NUm of blocks = " + NumberOfBlocks);
	}
	
	// ----------------------------------------------
	// Create Partition
	public int[][][] get()
	{
		// - - - Init Partition
		
		// Partition has C(n, k) blocks
		Partition = new int [NumberOfBlocks][][];
		for (int iB=0; iB < NumberOfBlocks; ++iB) 
		{
			// each of blocks has N levels
			Partition[iB] = new int [N][];
			
			// each of levels has at the most n_F elements 
			for (int iL = 0; iL < N; ++iL)
			{
				Partition[iB][iL] = new int[Seq.F(N)];
				// Set all of elements to ZERO
				for (int iE=0; iE < Seq.F(N); ++iE)
					Partition[iB][iL][iE] = 0;
			}
			
		}
		
		
		// Recurrence function to get partition
		solve(N, K, 0);
		
		return Partition;
	}
	
	
	// ----------------------------------------------
	// Recurrence function
	// _first - first block of that part
	//          we divide them in recurrence parts
	public void solve(int _n, int _k, int _first)
	{
		if (_k < 0) return;
		if (_n <= 0) return;
		if (_n < _k) return;
		
		
		
		// nF = Lk*kF + Lm*mF;
		// (n,k)F = Lk * SumK + Lm * SumM;
		
		int LambdaK, LambdaM, SumK, SumM;
		
		if (_k == _n)
		{
			LambdaK = 1;
			SumK = 1;
			
			LambdaM = 0;
			SumM = 0;
		}
		else
		if (_k == 0)
		{
			LambdaK = 0;
			SumK = 0;
			
			LambdaM = 1;//Seq.LambdaM(_n-_k, _k);
			SumM = 1;//(int)Seq.comb(_n-1, _k);
		}
		else
		{
			LambdaK = Seq.LambdaK(_n-_k, _k);
			SumK = (int)Seq.comb(_n-1, _k-1);
			
			LambdaM = Seq.LambdaM(_n-_k, _k);
			SumM = (int)Seq.comb(_n-1, _k);
			
		}
		
		if (_n == 4)
		System.out.println("_lM = " + LambdaM + " _lK = " + LambdaK + " SumM = " + SumM + " SumK = " + SumK);
		
		int iBlock = 0;
		int iElement = 0;
		
		// - - - - - - - - - - - - - - - - - - -
		// - - - - First class : correct points
		for (int iG = 0; iG < LambdaK; ++ iG)
		{	
			// each of groups has kF points
			for (int iE = 0; iE < Seq.F(_k); ++ iE)
			{
				// each of SumK block has the same elements 
				// on this level
				for (int iB = 0; iB < SumK; ++ iB)
				{
					Partition[_first + iG*SumK + iB][_n-1][iElement] = 1;
				}
				++ iElement;
			}
		}
		iBlock = _first + LambdaK * SumK;
		
		// - - - - - - - - - - - - - - - - - - -
		// - - - - Second class : incorrect points
		for (int iG = 0; iG < LambdaM; ++ iG)
		{
			// each of groups has mF points
			for (int iE = 0; iE < Seq.F(_n-_k); ++ iE)
			{
				// each of SumM block has the same elements 
				// on this level
				for (int iB = 0; iB < SumM; ++ iB)
				{
					Partition[iBlock + iG*SumM + iB][_n-1][iElement] = 2;
				}
				++ iElement;
			}
		}
		
		// Recurrency
		for (int iK=0; iK<LambdaK; ++ iK)
		{
			solve(_n-1, _k-1, _first + SumK*iK);
		}
		for (int iM=0; iM<LambdaM; ++ iM)
		{
			solve(_n-1, _k, _first + LambdaK * SumK + iM*SumM);
		}
		
		
		
		
	}
	
	// ----------------------------------------------
	// print partition
	public void print()
	{
		System.out.println("Partition:");
		for (int iB=0; iB < NumberOfBlocks; ++iB)
		{
			System.out.print(iB+1 + ". ");
			for (int iL=0; iL < M; ++iL)
			{
				System.out.print("{");
				for (int iE=0; iE < Seq.F(N); ++iE)
				{
					if (Partition[iB][iL][iE] > 0)
						//System.out.print(iE + "=");
						System.out.print(Partition[iB][iL][iE] + ",");
				}
				System.out.print("}");
			}
			System.out.println(" ");
		}
	}
	
}

// -----------------------------------------------------------------------------
// Draw everything with selected block
//
// -----------------------------------------------------------------------------
class CTileGraphics extends Applet
{
	// Private members
	private int N;		// Last level
	private int K;		// First level
	private int M;
	private CCobwebAdmissibleSeq Seq;	// Sequence
		
	private float SCALE = 1.0f;
	private int WIDTH;
	private int HEIGHT;
	private int OX = 30; 
	private int OY = 30;
	private Graphics g;
	
	// Colors
	Color cChain 		= new Color(210, 210, 210);
	Color cVertex 		= Color.black;
	Color cBlockChain 	= new Color(128, 0, 0);
	Color cBlockVertexOK = Color.red;
	Color cBlockVertexNO = Color.blue;
	
	// Hasse diagram
	// [x][] - level
	// [][x] - vertex
	private CVertex CobwebPoset[][];
	// Block of Partition
	private int Block[][];
	
	// ----------------------------------------------
	public CTileGraphics(int _n, int _k, CCobwebAdmissibleSeq _seq, int _block[][])
	{
		N = _n;
		K = _k;
		M = N - K;
		
		Seq = _seq;
		
		CobwebPoset = new CVertex[N][];
		Block = _block;
	}
	
	// ----------------------------------------------
	public void paint(Graphics _g)
	{
		WIDTH = getSize().width - 50;
		HEIGHT = getSize().height - 50;
		OY = getSize().height - 30;
		
		g = _g;
		Rectangle r = getBounds();
		g.setColor( Color.black );
		
		// 1. Draw arrangement
		draw_arrangement();
		
		// 2. Draw Hasse diagram
		draw_hasse();
		
		// 3. Draw selected block (in color)
		draw_block();
	}
	
	// ----------------------------------------------
	public void draw_block()
	{
		int iL, iV, iV2;
		if (Block == null) return;
		
		// draw chains of block
		g.setColor(cBlockChain);
		for (iL=0; iL < N-1; ++iL)
		{
			for (iV=0; iV < Seq.F(iL+1); ++iV)
			{
				if (Block[iL][iV] > 0)
				{
					// Find elements on upper level
					for (iV2=0; iV2 < Seq.F(iL+2); ++iV2)
					{
						if (Block[iL+1][iV2] > 0)
						{
							CobwebPoset[iL][iV].draw_chain(g, CobwebPoset[iL+1][iV2]);
						}
					}
				}
			}
		}
		
		// Draw block
		for (iL=0; iL < N; ++iL)
		{
			for (iV=0; iV < Seq.F(N); ++iV)
			{
				// Draw different kind of elements
				if (Block[iL][iV] == 1)
				{
					g.setColor(cBlockVertexOK);
					CobwebPoset[iL][iV].draw(g);
				}
				else
				if (Block[iL][iV] == 2)
				{
					g.setColor(cBlockVertexNO);
					CobwebPoset[iL][iV].draw(g);
				}
			}
		}
	}
	
	// ----------------------------------------------
	public void draw_hasse()
	{
		float DLevel = (float)HEIGHT / (float)(N-1);
		float DVertex = (float)(WIDTH-10) / (float)(Seq.F(N)-1);
		int iLevel, iL, iV, iV2, iVertex;
		
		// draw levels
		for (iL=0; iL < N; ++iL)
		{
			iLevel = OY - (int)(DLevel * iL);
			// level
			g.setColor(new Color(220,220,220));
			g.drawLine(OX, iLevel, OX+WIDTH, iLevel);
			
			// notation of level
			g.setColor(Color.black);
			g.setFont(new Font ( "Sans", Font.PLAIN, 10 ));
			g.drawString(new Integer(iL + 1).toString(), OX - 14, iLevel + 7);
			// symbol PHI
			g.drawOval(OX - 24, iLevel - 3, 8, 6);
			g.drawLine(OX - 20, iLevel - 5, OX - 20, iLevel + 5);
			g.drawLine(OX - 22, iLevel - 5, OX - 18, iLevel - 5);
			g.drawLine(OX - 22, iLevel + 5, OX - 18, iLevel + 5);
			
			// create Vertices
			CobwebPoset[iL] = new CVertex[Seq.F(iL+1)];
			for (iV=0; iV < Seq.F(iL+1); ++iV)
			{
				iVertex = OX + (int)(iV * DVertex) + 10;
				CobwebPoset[iL][iV] = new CVertex(iVertex, iLevel);
				//g.fillOval(iVertex, iLevel-5, 10, 10);
			}
		}
		
		// draw chains
		g.setColor(cChain);
		for (iL=0; iL < N-1; ++iL)
		{
			for (iV=0; iV < Seq.F(iL+1); ++iV)
			{
				// get vertex with vertex in next level
				for (iV2=0; iV2 < Seq.F(iL+2); ++iV2)
				{
					
					CobwebPoset[iL][iV].draw_chain(g, CobwebPoset[iL+1][iV2]);
				}
			}
		}
		
		// draw Vertices
		g.setColor(cVertex);
		for (iL=0; iL < N; ++iL)
		{
			for (iV=0; iV < Seq.F(iL+1); ++iV)
			{
				CobwebPoset[iL][iV].draw(g);
			}
		}
	}
	
	// ----------------------------------------------
	public void draw_arrangement()
	{	
		// Y axis
		g.drawLine(OX-1, OY+5, OX-1 , OY - HEIGHT-5);
		
		g.setColor(new Color(220,220,220));
		// X axis
		//g.drawLine(OX, OY, OX + WIDTH, OY);
	}
}

// -----------------------------------------------------------------------------
// Info:
// We calculate partition only when we change Numbers or size of layers, so
// Partition is global variable
// -----------------------------------------------------------------------------
public class MultiBlocks extends Applet implements ActionListener 
{
	// GUI members
	private Button bDraw, bPrev, bNext;
	private TextField tfK, tfN, tfCol;
	private Choice lSeq;
	Panel nPanel, panelMain, pGraph;
	private Label lab;
	
	// Default values
	private int K = 2;
	private int N = 5;
	private int Numbers = 1;
	private int Cols = 2; // number of columns to show diagrams
	private CCobwebAdmissibleSeq Seq;
	private int blockActual = 1;
	private int blockAll;
	
	
	// color of panel
	Color bg = new Color(180,180,180);
	Color bgN = new Color(200,200,200);
	
	// Tablet
	private CTileGraphics Tablet;
	private CTileGraphics Tablets[];
	
	// partition
	private int Partition[][][];
	
	// ----------------------------------------------
	public void init() {
		
		//CGetTile Tile = new CGetTile(6, 4, 1);
		//setBackground(new Color(0,150,0));
		
		// - - - GUI 
		setLayout( new BorderLayout() );
		
		add("North", paneTitle());
		add("South", paneOptSouth());
		
		// update N, K, other vars (1 - all)
		update(1);
		
		// -- -- -- -- -- -- -- -- -- -- -- -- -
		// -- Main graphics class - center panel
		//Tablet = new CTileGraphics(N, K, Seq, Partition[blockActual-1]);
		//Tablet.setBackground(Color.white);
		
		pGraph = new Panel(new GridLayout(Cols,2));
		
		Tablets = new CTileGraphics [(int)Seq.comb(N,K)];
		for (int i=0; i<(int)Seq.comb(N,K); ++i)
		{
			Tablets[i] = new CTileGraphics(N, K, Seq, Partition[i]);
			pGraph.add(Tablets[i]);
		}
		
		
		panelMain = new Panel();
		panelMain.setLayout(new BorderLayout());
		panelMain.setBackground(Color.white);
		panelMain.add("Center", pGraph);
		panelMain.add("South", paneOptNorth());
		
		add("Center", panelMain);
	}
	
	// ----------------------------------------------
	// 1 - all
	// 2 - without partition
	public void update(int _mode)
	{
		K = Integer.parseInt( tfK.getText() );
		N = Integer.parseInt( tfN.getText() );
		Numbers = lSeq.getSelectedIndex();
		Cols = Integer.parseInt( tfCol.getText() );
		
		// Create cobweb admissible sequence
		switch (Numbers)
		{
			default:
				Seq = new CSeqNatural();
				break;
				
			case 1:
				Seq = new CSeqFibonacci();
				break;
				
			case 2:
				Seq = new CSeqGauss();
				break;
		}
				
		blockAll = (int)Seq.comb(N, K);
		
		if (_mode == 1)
		{
			blockActual = 1;
			// Create and calculate new partition
			CGetPartition part = new CGetPartition(N, K, Seq);
			Partition = part.get();
		}
		
	}
	
	// ----------------------------------------------
	// print partition
	public void print()
	{
		System.out.println("Partition:");
		for (int iB=0; iB < blockAll; ++iB)
		{
			System.out.print(iB+1 + ". ");
			for (int iL=0; iL < (N - K + 1); ++iL)
			{
				System.out.print("{");
				for (int iE=0; iE < Seq.F(N); ++iE)
				{
					if (Partition[iB][iL][iE] > 0)
						//System.out.print(iE + "=");
						System.out.print(Partition[iB][iL][iE] + ",");
				}
				System.out.print("}");
			}
			System.out.println(" ");
		}
	}
	
	
	// ----------------------------------------------
	public void actionPerformed( ActionEvent evt )
	{
		if (evt.getSource() == bPrev )
		{
			if (K <= 0)
				K = N;
			else
				K --;
			tfK.setText(Integer.toString(K));
		}
		if (evt.getSource() == bNext )
		{
			if (K >= N)
				K = 0;
			else
				K ++;
				
			tfK.setText(Integer.toString(K));
		} 
		
		//if (evt.getSource() == bDraw )
		//{
		//	mode = 1;
		//}
		
		int mode = 1;
		
		remove ( pGraph );
		remove ( panelMain );
		
		
		update(mode);
		
		pGraph = new Panel(new GridLayout(Cols,2));
		
		//Tablet = new CTileGraphics(N, K, Seq, Partition[blockActual-1]);
		//Tablet.setBackground(Color.white);
		
		Tablets = new CTileGraphics [(int)Seq.comb(N,K)];
		for (int i=0; i<(int)Seq.comb(N,K); ++i)
		{
			Tablets[i] = new CTileGraphics(N, K, Seq, Partition[i]);
			Tablets[i].setBackground(Color.white);
			
			pGraph.add(Tablets[i]);
		}
		
		panelMain = new Panel();
		panelMain.setBackground(Color.white);
		panelMain.setLayout(new BorderLayout());
		panelMain.add("Center", pGraph);
		panelMain.add("South", paneOptNorth());
	
		add( "Center", panelMain);
		validate();
		
		
	}

	// ----------------------------------------------
	private Panel paneTitle()
	{
		// - - - Panel Title
		Panel pTitle = new Panel();	
		pTitle.setBackground(Color.gray);
		
		Label title = new Label("KoDAGs multi-blocks  P_{k,n-k}  in  Layer(N)");
		Font ft = new Font ( "Tahoma", Font.BOLD, 12 );
		
		title.setFont(ft);
		title.setBackground(Color.gray);
		title.setForeground(Color.white);
		pTitle.add(title);
		
		return pTitle;
	}
	
	// ----------------------------------------------
	private Panel paneOptNorth()
	{
		// - - - Panel Options North
		Panel nPanel = new Panel();
		nPanel.setBackground(bgN);
		nPanel.setLayout(new BorderLayout(0,0));
		
		Panel pCenter = new Panel();
		pCenter.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
		
		lab = new Label("Blocks: " + new Integer(blockAll).toString());
		lab.setFont(new Font("Dialog", Font.PLAIN, 11));
		pCenter.add(lab);
		
		// Control buttons
		Font ft = new Font( "Dialog", Font.PLAIN, 10 );
		
		
		bPrev = new Button("<<< PREV k ");
		bPrev.setFont(ft);
		bPrev.addActionListener(this);
		
		bNext = new Button(" NEXT k >>>");
		bNext.setFont(ft);
		bNext.addActionListener(this);
		
		nPanel.add("West", bPrev);
		nPanel.add("Center", pCenter);
		nPanel.add("East", bNext);
		
		return nPanel;
	}
	
	// ----------------------------------------------
	private Panel paneOptSouth()
	{
		// - - - Panel Options South
		Panel sPanel = new Panel();
		sPanel.setBackground(bg);
		lab = new Label("Sequence:");
		lab.setBackground(bg);
		sPanel.add(lab);
		
		lSeq = new Choice();
		lSeq.addItem("1 Natural numbers");
		lSeq.addItem("2 Fibonacci numbers");
		lSeq.addItem("3 Gaussian numbers");
		sPanel.add(lSeq);
		
		lab = new Label("Layer k:");
		lab.setBackground(bg);
		sPanel.add(lab);
		
		tfK = new TextField(new Integer(K).toString(),2);
		sPanel.add(tfK);
		
		lab = new Label("n:");
		lab.setBackground(bg);
		sPanel.add(lab);
		
		tfN = new TextField(new Integer(N).toString(),2);
		sPanel.add(tfN);
		
		lab = new Label("Cols:");
		lab.setBackground(bg);
		sPanel.add(lab);
		
		tfCol = new TextField(new Integer(Cols).toString(),2);
		sPanel.add (tfCol);
		
		bDraw = new Button(" C R E A T E ");
		Font ft = new Font( "Serif", Font.BOLD, 10 );
		bDraw.setFont(ft);
		bDraw.addActionListener(this);
		sPanel.add(bDraw);
		
		return sPanel;
	}
	

}

