/**
Rzecz to element listy jednokierunkowej.
*/
class Rzecz
{
    protected int wart ;
    protected Rzecz nast ;

    public Rzecz (int wart)
    {
        this.wart = wart ;
    }
    public Rzecz (int wart, Rzecz nast)
    {
        this(wart) ;
        this.nast = nast ;
    }

    public int wartosc ()
    {
        return wart ;
    }
    public Rzecz nastepnik ()
    {
        return nast ;
    }
    public void doczep (Rzecz nast) throws IllegalStateException
    {
        if (nast==null) throw new IllegalStateException() ;
        this.nast = nast ;
    }
}

/**
Klasa bazowa dla calego rynku obrotu towarem
*/
abstract class RynekZbytu
{
    protected volatile static boolean praca = true ;

    public synchronized static void stop ()
    {
        praca = false ;
    }
}

/**
Magazyn to opakowanie kolejki jednokierunkowej
z synchronizowanym dostepem.
Jest to zasob wspoldzielony przez konsumentow i producentow.
*/
class Magazyn extends RynekZbytu
{
    protected Rzecz poczatek, koniec ;
    protected int ilosc ;

    public synchronized int ilosc ()
    {
        return ilosc ;
    }
    public synchronized void dodaj (int x)
    {
        System.out.println(":"+Thread.currentThread().getName()+":-) zapelnienie") ;
        Rzecz rz = new Rzecz(x) ;
        if (ilosc==0) poczatek = rz ;
        else koniec.doczep(rz) ;
        koniec = rz ;
        ilosc++ ;
        notifyAll() ; // powiadomienie czekajacych konsumentow
        System.out.println(":"+Thread.currentThread().getName()+":-] powiadomienie") ;
    }
    public synchronized int usun ()
    {
        try
        {
            while (ilosc==0)
            {
                System.out.println(":"+Thread.currentThread().getName()+":-( pusty magazyn") ;
                wait(10000) ; // czekanie na zapelnienie magazynu
                if (!praca) return -1 ;
            }
        }
        catch (InterruptedException ex) { return -1 ; }
        int x = poczatek.wartosc() ;
        poczatek = poczatek.nastepnik() ;
        ilosc-- ;
        if (ilosc==0) koniec = null ;
        System.out.println(":"+Thread.currentThread().getName()+":-[ oproznienie") ;
        return x ;
    }
}

/**
Klasa producenta.
Jej zadaniem jest produkowanie towarow i dostarczanie ich do magazynu.
*/
class Producent extends RynekZbytu implements Runnable
{
    protected Magazyn magazyn ;
    public final String nazwa ;

    protected volatile static int licznik = 0 ;

    public Producent (Magazyn mag, String naz)
    {
        magazyn = mag ;
        nazwa = naz ;
    }

    public void run ()
    {
        System.out.println("-- "+nazwa+" rozpoczyna produkcje --") ;
        try { Thread.sleep((int)(Math.random()*9000+1000)) ; }
        catch (InterruptedException ex) { return ; }
        while (praca)
        {
            int x = ++licznik ; // (int)(Math.random()*10) ;
            System.out.println(nazwa+" dostarczyl towar "+x+" do magazynu") ;
            magazyn.dodaj(x) ;
            try { Thread.sleep((int)(Math.random()*9000+1000)) ; }
            catch (InterruptedException ex) { return ; }
        }
        System.out.println("-- "+nazwa+" konczy produkcje --") ;
    }
}

/**
Klasa konsumenta.
Jej zadaniem jest pobieranie towarow z magazynu i ich konsumowanie.
*/
class Konsument extends RynekZbytu implements Runnable
{
    protected Magazyn magazyn ;
    public final String nazwa ;

    public Konsument (Magazyn mag, String naz)
    {
        magazyn = mag ;
        nazwa = naz ;
    }

    public void run ()
    {
        System.out.println("-- "+nazwa+" rozpoczyna konsumpcje --") ;
        try { Thread.sleep((int)(Math.random()*9000+1000)) ; }
        catch (InterruptedException ex) { return ; }
        while (praca)
        {
            int x = magazyn.usun() ;
            System.out.println(nazwa+" pobral towar "+x+" z magazynu") ;
            try { Thread.sleep((int)(Math.random()*9000+1000)) ; }
            catch (InterruptedException ex) { return ; }
        }
        System.out.println("-- "+nazwa+" konczy konsumpcje --") ;
    }
}

/**
Klasa publiczna z programem do testowania
dzialania rynku obrotu towarami poprzez wspolny magazyn
z wykorzystaniem producentow i konsumentow.
*/
public class WatkiProdKons
{
    public static void main (String[] arg)
    {
        System.out.println("---- program start ----") ;
        Magazyn mag = new Magazyn() ;
        Thread[] prod = new Thread[2] ;
        for (int i=0 ; i<prod.length ; i++)
        {
            Producent pr = new Producent(mag,"producent-"+i) ;
            prod[i] = new Thread(pr,pr.nazwa) ;
            prod[i].start() ;
        }
        Thread[] kons = new Thread[2] ;
        for (int i=0 ; i<kons.length ; i++)
        {
            Konsument kn = new Konsument(mag,"konsument-"+i) ;
            kons[i] = new Thread(kn,kn.nazwa) ;
            kons[i].start() ;
        }
        try { Thread.sleep(30000) ; }
        catch (InterruptedException ex) { return ; }
        RynekZbytu.stop() ;
        System.out.println("---- program stop -----") ;
    }
}

/* przykladowe wyniki dzialania rogramu
---- program start ----
-- producent-1 rozpoczyna produkcje --
-- producent-0 rozpoczyna produkcje --
-- konsument-0 rozpoczyna konsumpcje --
-- konsument-1 rozpoczyna konsumpcje --
producent-0 dostarczyl towar 1 do magazynu
:producent-0:-) zapelnienie
:producent-0:-] powiadomienie
:konsument-1:-[ oproznienie
konsument-1 pobral towar 1 z magazynu
:konsument-0:-( pusty magazyn
producent-0 dostarczyl towar 2 do magazynu
:producent-0:-) zapelnienie
:producent-0:-] powiadomienie
:konsument-0:-[ oproznienie
konsument-0 pobral towar 2 z magazynu
:konsument-1:-( pusty magazyn
producent-1 dostarczyl towar 3 do magazynu
:producent-1:-) zapelnienie
:producent-1:-] powiadomienie
:konsument-1:-[ oproznienie
konsument-1 pobral towar 3 z magazynu
---- program stop -----
-- konsument-1 konczy konsumpcje --
-- producent-0 konczy produkcje --
-- konsument-0 konczy konsumpcje --
-- producent-1 konczy produkcje --
*/