using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Threading;

namespace Zakleszczenie
{
    class Program
    {
        class Konto
        {
            private decimal saldo;
            private int id;

            public Konto(decimal saldoPoczatkowe, int id)
            {
                saldo = saldoPoczatkowe;
                this.id = id;
            }

            /*
            int Id
            {
                get
                {
                    return id;
                }
            }
            */

            public void Wypłata(decimal kwota)
            {
                saldo -= kwota;
                //Console.WriteLine("Nastąpiła wypłata z konta {0} kwoty {1}. Saldo po operacji {2}.", id, kwota, saldo);
            }

            public void Wpłata(decimal kwota)
            {
                saldo += kwota;
                //Console.WriteLine("Nastąpiła wpłata na konto {0} kwoty {1}. Saldo po operacji {2}.", id, kwota, saldo);
            }

            //*
            public static void Przelew(Konto kontoPłatnika, Konto kontoOdbiorcy, decimal kwota)
            {
                if (kontoOdbiorcy == kontoPłatnika) throw new ArgumentException("Nie możliwe jest wykonanie przelewu na to samo konto");
              
                Console.WriteLine("Przygotowanie do przelewu z konta {0} na konto {1} kwoty {2}.", kontoPłatnika.id, kontoOdbiorcy.id, kwota);
                Console.WriteLine("Salda przed przelewem: konto {0} - saldo {1}, konto {2} - saldo {3}", kontoPłatnika.id, kontoPłatnika.saldo, kontoOdbiorcy.id, kontoOdbiorcy.saldo);
                lock (kontoPłatnika)
                {
                    Console.WriteLine("Dostęp do konta płatnika {0} zarezerwowany", kontoPłatnika.id);
                    Thread.Sleep(100);
                    lock (kontoOdbiorcy)
                    {
                        Console.WriteLine("Dostęp do konta odbiorcy {0} zarezerwowany", kontoOdbiorcy.id);                        
                        kontoPłatnika.Wypłata(kwota);
                        kontoOdbiorcy.Wpłata(kwota);
                    }
                    Console.WriteLine("Dostęp do konta odbiorcy {0} zwolniony", kontoOdbiorcy.id);
                }
                Console.WriteLine("Dostęp do konta płatnika {0} zwolniony", kontoPłatnika.id);
                Console.WriteLine("Wykonany został przelew z konta {0} na konto {1} kwoty {2}.", kontoPłatnika.id, kontoOdbiorcy.id, kwota);
                Console.WriteLine("Salda po przelewie: konto {0} - saldo {1}, konto {2} - saldo {3}", kontoPłatnika.id, kontoPłatnika.saldo, kontoOdbiorcy.id, kontoOdbiorcy.saldo);
            }
            //*/

            /*
            public static void Przelew(Konto kontoPłatnika, Konto kontoOdbiorcy, decimal kwota)
            {
                if (kontoOdbiorcy == kontoPłatnika) throw new ArgumentException("Nie możliwe jest wykonanie przelewu na to samo konto");

                Konto kontoA, kontoB;
                if (kontoPłatnika.id < kontoOdbiorcy.id)
                {
                    kontoA = kontoPłatnika;
                    kontoB = kontoOdbiorcy;
                }
                else
                {
                    kontoA = kontoOdbiorcy;
                    kontoB = kontoPłatnika;
                }

                Console.WriteLine("Przygotowanie do przelewu z konta {0} na konto {1} kwoty {2}.", kontoPłatnika.id, kontoOdbiorcy.id, kwota);
                Console.WriteLine("Salda przed przelewem: konto {0} - saldo {1}, konto {2} - saldo {3}", kontoPłatnika.id, kontoPłatnika.saldo, kontoOdbiorcy.id, kontoOdbiorcy.saldo);
                lock (kontoA)
                {
                    Console.WriteLine("Dostęp do konta {0} zarezerwowany", kontoA.id);
                    Thread.Sleep(100);
                    lock (kontoB)
                    {
                        Console.WriteLine("Dostęp do konta {0} zarezerwowany", kontoB.id);                        
                        kontoPłatnika.Wypłata(kwota);
                        kontoOdbiorcy.Wpłata(kwota);
                    }
                    Console.WriteLine("Dostęp do konta {0} zwolniony", kontoB.id);
                }
                Console.WriteLine("Dostęp do konta {0} zwolniony", kontoA.id);
                Console.WriteLine("Wykonany został przelew z konta {0} na konto {1} kwoty {2}.", kontoPłatnika.id, kontoOdbiorcy.id, kwota);
                Console.WriteLine("Salda po przelewie: konto {0} - saldo {1}, konto {2} - saldo {3}", kontoPłatnika.id, kontoPłatnika.saldo, kontoOdbiorcy.id, kontoOdbiorcy.saldo);
            }*/
        }

        class PoleceniePrzelewu
        {
            public Konto KontoPłatnika;
            public Konto KontoOdbiorcy;
            public decimal Kwota;
        }

        static void Main(string[] args)
        {
            Konto konto1 = new Konto(100, 1);
            Konto konto2 = new Konto(150, 2);

            WaitCallback transakcja = 
                (object parametr) =>
                {
                    PoleceniePrzelewu poleceniePrzelewu = parametr as PoleceniePrzelewu;
                    if (poleceniePrzelewu == null) throw new ArgumentNullException("Brak polecenia przelewu");
                    else Konto.Przelew(poleceniePrzelewu.KontoPłatnika, poleceniePrzelewu.KontoOdbiorcy, poleceniePrzelewu.Kwota);
                };
            ThreadPool.QueueUserWorkItem(transakcja, new PoleceniePrzelewu { KontoPłatnika = konto1, KontoOdbiorcy = konto2, Kwota = 50 });
            //ThreadPool.QueueUserWorkItem(transakcja, new PoleceniePrzelewu { KontoPłatnika = konto1, KontoOdbiorcy = konto2, Kwota = 10 });
            ThreadPool.QueueUserWorkItem(transakcja, new PoleceniePrzelewu { KontoPłatnika = konto2, KontoOdbiorcy = konto1, Kwota = 10 });
            
            Console.ReadLine(); //wątek główny czeka na dodatkowe wątki
            
        }
    }
}
