import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.awt.event.*;

public class m extends JFrame 
	implements MouseMotionListener,MouseListener,KeyListener {

static final int HIGH=10;
static final int LOW=0;

volatile int MINCLOCK=2;
volatile int TDUR=MINCLOCK*4;
volatile int STEP=MINCLOCK;

private void setClock(int c)
{
	if(c>1 && c<20) {
		MINCLOCK=c;
		TDUR=MINCLOCK*4;
		STEP=MINCLOCK;
	}
}

public static void main(String[] args)
{
    new m();
}

private Pan pan;
private Thread t;

private Program prog;
private Thread pt;

private Client cli;
private Thread ct;

public m()
{
    pan = new Pan();
	this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.addMouseListener(this);
    this.addMouseMotionListener(this);
    this.addKeyListener(this);
    this.getContentPane().add(pan, BorderLayout.CENTER);
    this.setSize(1050,300);
    this.setVisible(true);

    t=new Thread(pan);

    prog = new Program(pan);
    pt=new Thread(prog);

    cli = new Client(pan);
    ct=new Thread(cli);

    t.start();
    pt.start();
    ct.start();
}

volatile boolean bRunning=true;
volatile int mStopX=0;
public boolean isRunning() {return bRunning;}
public int getStopX() {return mStopX;}

public void mousePressed(MouseEvent e)
{
    mStopX=e.getX();
    bRunning=false;
}
      
public void mouseClicked(MouseEvent e) {}
      
public void mouseReleased(MouseEvent e)
{
    mStopX=e.getX();
    bRunning=true;
}

public void mouseDragged(MouseEvent e) {
    mStopX=e.getX();
}

public void mouseMoved(MouseEvent e) {}

public void mouseEntered(MouseEvent e) {}

public void mouseExited(MouseEvent e) {}

public void keyTyped(KeyEvent e) {}

public void keyPressed(KeyEvent e) {
	char ch = e.getKeyChar();
	if(ch=='+') setClock(MINCLOCK-1);
	if(ch=='-') setClock(MINCLOCK+1);
}

public void keyReleased(KeyEvent e) {}

class Program implements Runnable
{
    private Pan pan;
    public Program(Pan pan)
    {
        this.pan=pan;
    }

    private Random rand=null;

    private void genData()
    {
        if(rand==null) rand = new Random(System.currentTimeMillis());
        pan.set(0,rand.nextInt()&1);
        pan.set(1,rand.nextInt()&1);
    }

    private void sleep(int ms) {
        try{Thread.sleep(ms);}catch(Exception ex){}
    }

    int bits=8;
    int mask=0x80;
    public void run()
    {
        int l=0;
        pan.set(0,HIGH);
        pan.set(1,0);
        pan.set(2,0);
        sleep(1000);
        while(true) {
            if(!isRunning()) {sleep(MINCLOCK);continue;}
            sleep(MINCLOCK*20);
            pan.set(0,0);
            int i;
            int v=l;
            for(i=0;i<bits;i++) {
                pan.set(2,(v&mask)>0?HIGH:0);
                sleep(TDUR);//wait data reise
                pan.set(1,HIGH);
                sleep(TDUR);//wait read
                pan.set(1,0);
                pan.set(2,0);
                v<<=1;
            }
            l++;
            sleep(TDUR);
            pan.set(0,HIGH);
        }
    }
}

class Client implements Runnable
{
    private Pan pan;
    public Client(Pan pan)
    {
        this.pan=pan;
    }

    private void sleep(int ms) {
        try{Thread.sleep(ms);}catch(Exception ex){}
    }

    public void run()
    {
        int value=0;
        boolean started=false;
        boolean consumed=false;
        while(true) {
            if(!isRunning()) {sleep(5);continue;}
            if(pan.get(0)==0) {//CS
                sleep(MINCLOCK);
                //pan.set(3,1);
                if(pan.get(1)==HIGH) {//SCK
                    if(!started) {
                        System.out.print("receiving:");
                        started=true;
                    }
                    //sleep(10);
                    int v=pan.get(2);//data in
                    if(!consumed) {
                        value<<=1;
                        if(v==HIGH) value|=1;
                        consumed=true;
                        System.out.print(v==0?"0":"1");
                        pan.set(3,v==0?HIGH:0);
                    }
                    //sleep(100);
                } else {
                    consumed=false;
                }
            } else {
                if(started) {
                    System.out.println(" -> "+value);
                    started=false;
                    value=0;
                }
                pan.set(3,0);//clear data out
            }
        }
    }
}

class Pan extends JPanel implements Runnable
{
    int values[];
    int[][] d;
    String[] names;
    int channelLen=400;
    int channels=4;
    public Pan()
    {
        d = new int[channels][];
        names = new String[channels];
        names[0]="!SS";
        names[1]="SCK";
        names[2]="MOSI";
        names[3]="MISO";
        values = new int[d.length];
        int i;
        for(i=0;i<d.length;i++)
            d[i] = new int[channelLen];
        //for(i=0;i<channelLen;i++) d[0][i]=&1;
        //for(i=0;i<channelLen;i++) d[1][i]=(i&2)>>1;
    }

    private void sleep(int ms) {
        try{Thread.sleep(ms);}catch(Exception ex){}
    }

    public void set(int channel, int value)
    {
        int oldv=get(channel);
        while(!isRunning()) {sleep(MINCLOCK);}
        if(oldv>value) {
            while(value!=oldv) {
                while(!isRunning()) {sleep(MINCLOCK);}
                sleep(STEP);
                while(!isRunning()) {sleep(MINCLOCK);}
                synchronized(values) {
                    values[channel]=oldv--;
                }
            }
        } else if(oldv<value) {
            while(value!=oldv) {
                while(!isRunning()) {sleep(MINCLOCK);}
                sleep(STEP);
                while(!isRunning()) {sleep(MINCLOCK);}
                synchronized(values) {
                    values[channel]=oldv++;
                }
            }
        }
        while(!isRunning()) {sleep(MINCLOCK);}
        synchronized(values) {
            values[channel]=value;
        }
    }

    public int get(int channel)
    {
        synchronized(values) {
            return values[channel];
        }
    }

    int xscale=2;
    int yscale=2;
    int bottomOffset=10;
    int leftOffset=20;

    private int getYOffset(Graphics g, int channel)
    {
        int h=this.getHeight()-bottomOffset;
        return h-=(channels-channel)*(channels*HIGH+10);
    }
    private void draw(Graphics g, int channel, int x1, int y1, int x2, int y2)
    {
        //int h=this.getHeight()-bottomOffset;
        //h-=(channels-channel)*(channels*HIGH+10);
        int h = getYOffset(g,channel);
        g.drawLine(x1*xscale,h-y1*yscale,x2*xscale,h-y2*yscale);
    }

    private void read()
    {
        int id,i;
        for(id=0;id<d.length;id++) {
            int[] dd = d[id];
            for(i=0;i<dd.length-1;i++) {
                dd[i]=dd[i+1];
            }
            synchronized(values) {
                dd[i]=values[id];
            }
        }
    }

    public void paint(Graphics g)
    {
        super.paint(g);
        int id,i;
        for(id=0;id<d.length;id++) {
            int yoh = getYOffset(g,id);
            g.drawString(names[id],0,yoh-10);
            int[] dd = d[id];
            int x=leftOffset;
            int prev=-1;
            for(i=0;i<dd.length;i++,x++) {
                int v = dd[i];
                draw(g,id,x,v,x+1,v);
                if(prev!=-1) {
                    draw(g,id,x,prev,x,v);
                }
                prev=v;
            }
        }
        if(!isRunning()) {
            int mx = getStopX();
            g.drawLine(mx,0,mx,this.getHeight());
        }
    }

    public void run()
    {
        while(true) {
            this.repaint();
            try{Thread.sleep(MINCLOCK);}catch(Exception ex){}
            if(isRunning())
                read();
        }
    }
}

};


