/* Martynas Remeika PS-3

   ADS3 5 uzd:
   Bankas (modeliavimas su eile ir pririotetine eile). 
   Procesas 1: klientai ateina i banka ir kreipiasi i bet kuri laisva kasininka, 
   jei visi uzsieme, klientai stoja i bendra eile, taciau yra VIP klientai, kurie 
   aptarnaujami be eiles, zinoma, jei visi kasininkai uzimti VIP klientas irgi turi 
   laukti, netgi gali susidaryti VIP eile.
   Procesas 2: bankas turi atskirus kasininkus, kurie dirba su paprastais ir VIP
   klientais. Pradiniai duomenys: paprasto ir VIP kliento atejimo tikimybes, paprastus
   ir VIP klientus aptarnaujanciu kasininku skaicius, paprasto ir VIP kliento aptarnavimo
   laikas, darbo dienos ilgis. Ivertinti kuris procesas tikslingesnis bankui. Vertinama
   pagal tokius kriterijus:
        1. Suminis kasininku prastovu laikas.
		2. Dvigubas suminis kasininku virsvalandziu laikas (visi klientai kurie atejo
		   iki darbo pabaigos turi buti aptarnauti)
		3. Trigubas vidutinis paprasto kliento laukimo laikas.
		4. Desimtgubas maksimalus VIP kliento laukimo laikas.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define MAX 65535

struct eile {
    int Priority;        // 0 - normal
    int ArrivalTime;     // laikas kada klentas uzejo i banka
    int WaitedTime;      // laikas kuri klientas praleido laukdamas eileje
    struct eile *next;
};
typedef struct eile *LINE;

struct eile2 {
    int BusyOrNot;        // 0 - free, 1 - busy
    int BusyWithVIPOrNot; // 0 - not VIP, 1 - VIP
    int ArrivalTime;      // laikas kada klientas pradetas aptarnauti
    int WaitedTime;       // prastovos laikas kol kasininkas sedi be darbo
    int OverTime;
    struct eile2 *next;
};
typedef struct eile2 *ACCOUNTANT;

void Data(int *Normal, int *VIP, int *NormalAccountants, int *VIPAccountants, int *NormalTime, int *VIPTime, int *WorkDuration);
void DeleteFile();
void CreateClientsLine (LINE *head);
void CreateAccountantLine (ACCOUNTANT *head, int count);
int IsEmpty (LINE head);
void InsertToLine (LINE *head, int Priority, int ArrivalTime, int WaitedTime);
void RemoveOldestNormal (LINE *head);
void RemoveOldestVIP (LINE *head);
void PrintLine (LINE head);
void IncreaseClientsWaitedTime (LINE head);
void IncreaseAccountantOverTime (ACCOUNTANT *head, int CurrentTime, int WorkDuration);
void Add (int CurrentTime, int VIP, int Normal, int *ClientsP, int *VipClientsP);
void SendClientsToAccountants (LINE *LineHead, ACCOUNTANT VIPAccountants, ACCOUNTANT NormalAccountants, LINE *done, int CurrentTime);
void DoneOrNot (ACCOUNTANT *head, int CurrentTime, int NormalTime, int VIPTime);
int working (ACCOUNTANT head);

/* Duomenu nuskaitymas is klaviaturos */
void Data(int *Normal, int *VIP, int *NormalAccountants, int *VIPAccountants, int *NormalTime, int *VIPTime, int *WorkDuration) {
    int NormalT, VipT;
    int NormalAccountantsT, VIPAccountantsT;
    int NormalTimeT, VIPTimeT;
    int WorkDurationT;
	
    do {
        printf("Ivesk paprasto ir vip kliento atejimo tikimybes (%): ");
        scanf("%d %d", &NormalT, &VipT);
    } while ((NormalT + VipT) > 200);

    printf("Ivesk klientu ir vip klientu aptarnavimo kasu skaiciu: ");
    scanf("%d %d", &NormalAccountantsT, &VIPAccountantsT);
    printf("Ivesk paprasto ir vip kliento aptarnavimpo laika (min): ");
    scanf("%d %d", &NormalTimeT, &VIPTimeT);
    printf("Ivesk darbo dienos ilgi (min): ");
    scanf("%d", &WorkDurationT);

    *Normal = NormalT; *VIP = VipT; *NormalAccountants = NormalAccountantsT; *VIPAccountants = VIPAccountantsT;
    *NormalTime = NormalTimeT; *VIPTime = VIPTimeT; *WorkDuration = WorkDurationT;
}

/* Rezultato failo istrinimas */
void DeleteFile() {
	FILE *Output;
	Output = fopen("rez.txt", "w");
	fclose(Output);
}

/* Sukuriam kasininku linija */
void CreateAccountantLine (ACCOUNTANT *head, int count) {
    int i;
    *head = NULL;
    for (i = 0; i < count; i++) {
        ACCOUNTANT temp = (struct eile2*) malloc (sizeof (struct eile2));
        temp->BusyOrNot = 0;
        temp->BusyWithVIPOrNot = 0;
        temp->ArrivalTime = 0;
        temp->WaitedTime = 0;
        temp->OverTime = 0;
        temp->next = NULL;
        ACCOUNTANT temphead = *head;
        if (NULL != *head) {
            while (NULL != temphead->next) temphead = temphead->next;
            temphead->next = temp;
        }
        else *head = temp;
    }
}

/* Funkcija sukurianti tuscia banko eile */
void CreateClientsLine (LINE *head) {
    *head = NULL;
}

/* Funkcija patikrinanti ar eile tuscia */
int IsEmpty (LINE head) {
    if (NULL == head) return 1;
    else return 0;
}

/* Funkcija modeliuojanti banko eile */
void InsertToLine (LINE *head, int Priority, int ArrivalTime, int WaitedTime) {
    LINE temp = (struct eile*) malloc (sizeof (struct eile));
    temp->next = NULL;
    if (1 == Priority) temp->Priority = 1;  // VIP priority
    else temp->Priority = 0;         // normal priority
    temp->ArrivalTime = ArrivalTime;
    temp->WaitedTime = WaitedTime;
    LINE temphead = *head;
    if (! IsEmpty (*head)) {
        if (0 == temp->Priority) {
            while (NULL != temphead->next) temphead = temphead->next;
            temphead->next = temp;
        }
        else {
            if (0 == (*head)->Priority) {
                temp->next = *head;
                *head = temp;
            }
            else {
                while ((NULL != temphead->next) && (1 == temphead->next->Priority)) {
                    temphead = temphead->next;
                }
                temp->next = temphead->next;
                temphead->next = temp;
            }
        }
    }
    else *head = temp;
}

/* Funkcija is eiles ismetanti VIP klienta */
void RemoveOldestVIP (LINE *head) {
    LINE temp = *head;
    if ((NULL != temp->next) && (1 == temp->Priority)) {
        *head = (*head)->next;
        free (temp);
    }
    else if ((NULL == temp->next) && (1 == temp->Priority)) {
        free (*head);
        *head = NULL;
    }
}

/* Funkcija is eiles ismetanti paprasta klienta */
void RemoveOldestNormal (LINE *head) {
    LINE temp = *head, help;

	// jei pirmas yra paprastas klientas
	if ((NULL != temp->next) && (0 == temp->Priority)) {
        *head = (*head)->next;
        free (temp);
    }
    else if ((NULL == temp->next) && (0 == temp->Priority)) { // paprastas ir vienintelis
        free (*head);
        *head = NULL;
    }
    else if ((NULL != temp->next) && (1 == temp->Priority)) {
        while ((NULL != temp->next) && (0 != temp->next->Priority))
            temp = temp->next;
        help = temp->next;
        temp->next = temp->next->next;
        free (help);
	}
}

/* Funkcija atspausdinanti sarasa (nereikalinga) */
void PrintLine (LINE head) {
	FILE *output;
	output = fopen("rez.txt", "a");
    fprintf (output, "Eile:\n");
    while (NULL != head) {
        fprintf (output, "%d %d %d\n", head->Priority, head->ArrivalTime, head->WaitedTime);
        head = head->next;
    }
	fclose(output);
}

/* Funkcija padidinanti laika kuri pralauke eileje */
void IncreaseClientsWaitedTime (LINE head) {
    while (NULL != head) {
        (head)->WaitedTime++;
        head = head->next;
    }
}

/* Funkcija pagal tikimybe i eile pridedantis klientu */
void Add (int CurrentTime, int VIP, int Normal, int *ClientsP, int *VipClientsP) {
    int temp = 0, come = 1;
	int CountNormal = 0, CountVip = 0;
    while (1 == come) {
        if (VIP > (temp = rand() % 101)) {
          CountVip++;
        }
        else come = 0;
    }
	VipClientsP[CurrentTime] = CountVip;
    come = 1;
    while (1 == come) {
        if (Normal > (temp = rand() % 101)) {
          CountNormal++;
        }
        else come = 0;
    }
	ClientsP[CurrentTime] = CountNormal;
}

/* Funkcija is eiles klientas padedanti prie kasininku */
void SendClientsToAccountants (LINE *LineHead, ACCOUNTANT VIPAccountants, ACCOUNTANT NormalAccountants, LINE *done, int CurrentTime) {
    // tvarkomes su vip klientais
    while (NULL != VIPAccountants) {
        if (0 == VIPAccountants->BusyOrNot) {
            if ((NULL != *LineHead) && (1 == (*LineHead)->Priority)) {
                VIPAccountants->BusyOrNot = 1; // pazymin kaip uzimta
                VIPAccountants->BusyWithVIPOrNot = 1;// pazymim kad su vip klientu uzsimam
                VIPAccountants->ArrivalTime = CurrentTime; // kasininkui pasakom kada pradejo aptarnaut
                InsertToLine (done, (*LineHead)->Priority, (*LineHead)->ArrivalTime, (*LineHead)->WaitedTime); // dabar aptarnaujamaji i islaukusiu eile sarasa ikisu
                RemoveOldestVIP (LineHead);
            }
        }
        VIPAccountants = VIPAccountants->next;
    }
    
    LINE temp = *LineHead;
    while ((NULL != temp) && (0 != temp->Priority))
        temp = temp->next;
    // ziurim gal tiems paprastiems klientams yra laisvu kasu
    while (NULL != NormalAccountants) {
        if (0 == NormalAccountants->BusyOrNot) {
            if ((NULL != temp) && (0 == temp->Priority)) {
                NormalAccountants->BusyOrNot = 1;
                NormalAccountants->BusyWithVIPOrNot = 0;
                NormalAccountants->ArrivalTime = CurrentTime; // kasininkui pasakom kada pradejo aptarnaut
                InsertToLine (done, temp->Priority, temp->ArrivalTime, temp->WaitedTime); // dabar aptarnaujamaji i islaukusiu eile sarasa ikisu
                RemoveOldestNormal (LineHead);
            }
        }
        NormalAccountants = NormalAccountants->next;
    }
}

/* Jei kasininkas nedirba, didinam jo prastovos laika */
/* Pakeista f-ja */
void IncreaseAccountantWaitedTime (ACCOUNTANT head, int CurrentTime, int WorkTime) {
    while (NULL != head) {
        if ((0 == head->BusyOrNot)&&(CurrentTime<=WorkTime)) head->WaitedTime++;
        head = head->next;
    }
}

/* Patikrina ar kuris nors kasininkas atidirbo su klientu */
void DoneOrNot (ACCOUNTANT *head, int CurrentTime, int NormalTime, int VIPTime) {
    ACCOUNTANT temp = *head;
    int WorkWithClientTime = 0;
    while (NULL != temp) {
        if (1 == temp->BusyOrNot) {
            WorkWithClientTime = CurrentTime - temp->ArrivalTime;
            if (0 == temp->BusyWithVIPOrNot) {
                if (NormalTime == WorkWithClientTime) {
                    temp->BusyWithVIPOrNot = 0;
                    temp->BusyOrNot = 0;
                }
            }
            else if (1 == temp->BusyWithVIPOrNot) {
                if (VIPTime == WorkWithClientTime) {
                    temp->BusyWithVIPOrNot = 0;
                    temp->BusyOrNot = 0;
                }
            }
        }
        temp = temp->next;
    }
}

/* Funkcija padidinanti kasininku virsvalandziu skaitliukus */
void IncreaseAccountantOverTime (ACCOUNTANT *head, int CurrentTime, int WorkDuration) {
    ACCOUNTANT temp = *head;
    if (CurrentTime > WorkDuration) {
        while (NULL != temp) {
            if (1 == temp->BusyOrNot) temp->OverTime++;
            temp = temp->next;
        }
    }
}

/* Funkcija pasakanti ar bent vienas kasininkas dirba */
int working (ACCOUNTANT head) {
    int temp = 0;
    while (NULL != head) {
        if (1 == head->BusyOrNot) temp = 1;
        head= head->next;
    }
    return temp;
}

/* Parodo proceso statistika */
void ShowResults (ACCOUNTANT NormalAccountants, ACCOUNTANT VIPAccountants, LINE done, int *rez, int kas) {
    int WastedTimeSum = 0, OverTimeSum = 0;
	
    while (NULL != NormalAccountants) {
        WastedTimeSum = WastedTimeSum + NormalAccountants->WaitedTime;
        OverTimeSum = OverTimeSum + NormalAccountants->OverTime;
        NormalAccountants = NormalAccountants->next;
    }

	while (NULL != VIPAccountants) {
		WastedTimeSum = WastedTimeSum + VIPAccountants->WaitedTime;
        OverTimeSum = OverTimeSum + VIPAccountants->OverTime;
        VIPAccountants = VIPAccountants->next;
    }


    OverTimeSum = OverTimeSum * 2;
    
    int WaitingTimeNormal = 0, CountNormal = 0, MaxWaitingTimeVIP = 0, ats = 0;
    while (NULL != done) {
        if ((0 == done->Priority) && (0 != done->WaitedTime)) {
            WaitingTimeNormal = WaitingTimeNormal + done->WaitedTime;
            CountNormal++;
        }
        else if (1 == done->Priority) {
            if (MaxWaitingTimeVIP < done->WaitedTime)
                MaxWaitingTimeVIP = done->WaitedTime;
        }
        done = done->next;
    }
	if(CountNormal != 0)
        ats = 3 * (WaitingTimeNormal) / CountNormal;
	if(ats < 0)
		ats=-ats;
    MaxWaitingTimeVIP = MaxWaitingTimeVIP * 10;
    float total = 0;
	
	rez[0]=WastedTimeSum;
	rez[1]=OverTimeSum;
	rez[2]=abs(ats);
	rez[3]=MaxWaitingTimeVIP;
}

int Which(int *a, int *b) {
	int sum1=0,	sum2=0;
	int i;
	for(i=0; i<4; i++) {
		sum1 = sum1 + a[i];
		}
	sum2 = b[0] + b[1] + b[2] + (int)(b[3]/10);
	if(sum1 < sum2)
		return 1;
	else
		return 2;
}

int Ok(int *a, int Clients, int VIP, int WorkDuration) {
	int WastedTime, OverTime, AverageTime, MaxVipTime;
	int Count = Clients + VIP;
	int OkWastedTime, OkOverTime, OkAverageTime, OkMaxVipTime;   // musu kriterijai
	
	WastedTime = abs(a[0] / Count);         // Isnaujo paskaiciuojam laikus
	OverTime = abs( (a[1] / 2) / Count );
	AverageTime = abs(a[2] / 3);
	MaxVipTime = abs(a[3] / 10);

	OkWastedTime = abs(WorkDuration * 0.3); // kriterijai kurie tenkina salygas
	OkOverTime = 30;
	OkAverageTime = 40;
	OkMaxVipTime = 40;
	
	if( (OverTime > OkOverTime)||(AverageTime > OkAverageTime)||(MaxVipTime > OkMaxVipTime)||(OkWastedTime < WastedTime) )
		return 1;                    // grazinam kad reikia didinti kasininku skaiciu
	else
		return 0;
}

/* Modeliavimas abieju procesu */
void Model(int *Arez, int *Brez, int Normal, int VIP, int NormalAccountants, int VIPAccountants, int NormalTime, int VIPTime,
		   int WorkDuration, int *ClientsP, int *VipClientsP) {

	int i;
	int CurrentTime = 0;
	int Accountants  = NormalAccountants + VIPAccountants;

	// 1 Procesas
    LINE general, made;
    CreateClientsLine (&made);
    CreateClientsLine (&general);
    ACCOUNTANT line;
    CreateAccountantLine (&line, Accountants);

    while ((CurrentTime < WorkDuration) || (1 == working (line))) {
        if ((CurrentTime < WorkDuration)&&( (ClientsP[CurrentTime]!=-1) || (VipClientsP[CurrentTime]!=-1) )) {
			if(ClientsP[CurrentTime]!=-1) 
				for(i=0; i<ClientsP[CurrentTime]; i++)
					InsertToLine (&general, 0, CurrentTime, 0);
			if(VipClientsP[CurrentTime]!=-1)
				for(i=0; i<VipClientsP[CurrentTime]; i++)	
					InsertToLine (&general, 1, CurrentTime, 0);
		}	
        DoneOrNot (&line, CurrentTime, NormalTime, VIPTime);
        SendClientsToAccountants (&general, line, line, &made, CurrentTime);
        IncreaseClientsWaitedTime (general);
        IncreaseAccountantWaitedTime (line, CurrentTime, WorkDuration);
        IncreaseAccountantOverTime (&line, CurrentTime, WorkDuration);
        CurrentTime++;
    }
	
	
	// 2 procesas
	CurrentTime = 0;
	LINE Clients, Vip, made2;
	CreateClientsLine(&Clients);
	CreateClientsLine(&Vip);
	CreateClientsLine(&made2);
	ACCOUNTANT aclients, avip;
	CreateAccountantLine (&avip, VIPAccountants);
	CreateAccountantLine (&aclients, NormalAccountants);

	while ((CurrentTime < WorkDuration)|| (1 == working (avip) || working(aclients) == 1)) {
		if ((CurrentTime < WorkDuration)&&( (ClientsP[CurrentTime]!=-1) || (VipClientsP[CurrentTime]!=-1) )) {
			if(ClientsP[CurrentTime]!=-1) 
				for(i=0; i<ClientsP[CurrentTime]; i++)
					InsertToLine (&Clients, 0, CurrentTime, 0);
			if(VipClientsP[CurrentTime]!=-1)
				for(i=0; i<VipClientsP[CurrentTime]; i++)	
					InsertToLine (&Vip, 1, CurrentTime, 0);
	}
		DoneOrNot (&aclients, CurrentTime, NormalTime, VIPTime);
		DoneOrNot (&avip, CurrentTime, NormalTime, VIPTime);
        SendClientsToAccountants (&Clients, aclients, avip, &made2, CurrentTime);
		SendClientsToAccountants (&Vip, aclients, avip, &made2, CurrentTime);
        IncreaseClientsWaitedTime (Clients);
		IncreaseClientsWaitedTime (Vip);
        IncreaseAccountantWaitedTime (aclients, CurrentTime, WorkDuration);
		IncreaseAccountantWaitedTime (avip, CurrentTime, WorkDuration);
        IncreaseAccountantOverTime (&avip, CurrentTime, WorkDuration);
		IncreaseAccountantOverTime (&aclients, CurrentTime, WorkDuration);
        CurrentTime++;
    }

	ShowResults (line,NULL, made, Arez, 0);
	ShowResults (aclients, avip, made2, Brez, 1);
	
   	//if (!IsEmpty(made)) PrintLine (made);
	//if (!IsEmpty(made)) PrintLine (made2);

	general=NULL; made=NULL; line=NULL;
	Clients=NULL; Vip=NULL; made2=NULL; avip=NULL; aclients=NULL;
	free(general); free(made); free(line);
	free(Clients); free(Vip); free(made2); free(avip); free(aclients); 

}

/* Funkcija rezultatu isvedimui i faila */
void ToFile(int *Arez, int *Brez) {
	FILE *Output;
	Output = fopen("rez.txt", "a");
	fprintf(Output, "1 Procesas:\n\n");
	fprintf(Output, "Suminis kasininku laukimo laikas: %d\n", Arez[0]);
	fprintf(Output, "Suminis dvigubas kasininku virsvalandziu laikas: %d\n", Arez[1]);
	fprintf(Output, "Trigubas vidutinis paprasto kliento laukimo laikas: %d\n", Arez[2]);
	fprintf(Output, "Desimtgubas maksimalus VIP kliento laukimo laikas: %d\n\n", Arez[3]);
	fprintf(Output, "Procesas 2:\n\n");
	fprintf(Output, "Suminis kasininku laukimo laikas: %d\n", Brez[0]);
	fprintf(Output, "Suminis dvigubas kasininku virsvalandziu laikas: %d\n", Brez[1]);
	fprintf(Output, "Trigubas vidutinis paprasto kliento laukimo laikas: %d\n", Brez[2]);
	fprintf(Output, "Desimtgubas maksimalus VIP kliento laukimo laikas: %d\n\n", Brez[3]/10);	
	fclose(Output);
}

int main() {
	int WorkDuration=480;
    int VIPAccountants=2, NormalAccountants=2;
    int NormalTime=15, VIPTime=20;
    int VIP=90, Normal=90;  // tikimybes
    int CurrentTime = 0;

	int ClientsP[MAX], VipClientsP[MAX]; 
	int Arez[4], Brez[4];
	int i;
	int kiek;
	int kiekp = 0, kiekvip = 0;
	printf("Ivesk kiek kartu kartoti visa modeliavimo situacija: ");
	scanf("%d", &kiek);
	Data(&Normal, &VIP, &NormalAccountants, &VIPAccountants, &NormalTime, &VIPTime, &WorkDuration);
	int Normalt = Normal; int VIPt = VIP;
	Normal = abs(Normal * 0.45);
	VIP = abs(VIP * 0.45);

	
	
	for(i=0; i<kiek; i++) {
	for(i=0; i<MAX; i++) { ClientsP[i] = -1 ; VipClientsP[i] = -1; }
    for(i=0; i<WorkDuration; i++) {
		Add (CurrentTime, VIP, Normal, ClientsP, VipClientsP);
		CurrentTime++;
	}
	}

	for(i=1; i<MAX; i++) {
		if(ClientsP[i] != -1) 
			kiekp += ClientsP[i];
		if(VipClientsP[i] != -1)
			kiekvip += VipClientsP[i];
	}

	Model(Arez, Brez, Normal, VIP, NormalAccountants, VIPAccountants, NormalTime, VIPTime, WorkDuration, ClientsP, VipClientsP);
	DeleteFile();
	ToFile(Arez, Brez);

	int Status=1;
	NormalAccountants = 0; VIPAccountants = 1;
	while( Status != 0) {
		NormalAccountants++; 
		Model(Arez, Brez, Normal, VIP, NormalAccountants, VIPAccountants, NormalTime, VIPTime, WorkDuration, ClientsP, VipClientsP);
		Status = Ok(Arez,NormalAccountants, VIPAccountants, WorkDuration);
	}
	int Count = NormalAccountants + VIPAccountants;
	
	
	
/*	NormalAccountants = abs( (Count * Normalt) / 100);
	VIPAccountants = abs( (Count * VIPt)/100);
	if(NormalAccountants + VIPAccountants < Count)
		NormalAccountants++;
 */	

/*	if((Count % 2) != 0)
		NormalAccountants = (Count / 2) + 1;
	else
		NormalAccountants = Count / 2;
	VIPAccountants = Count / 2;
	//NormalAccountants = 8;
	//VIPAccountants = 2;
	
	Count * P / PP + VIPP;
*/
	NormalAccountants = abs ((Count * Normalt)/(Normalt + VIPt));
	VIPAccountants = abs ((Count * VIPt)/(Normalt + VIPt)) + 1;
	//printf("%d %d", NormalAccountants, VIPAccountants);

	Model(Arez, Brez, Normal, VIP, VIPAccountants, NormalAccountants, NormalTime, VIPTime, WorkDuration, ClientsP, VipClientsP);
	ToFile(Arez, Brez);

	FILE *Output;
	Output = fopen("rez.txt", "a");
	
	if( Which(Arez, Brez) == 1)
		fprintf(Output, "\nPrie tokio apkrovimo geresnis 1 procesas. Optimalus bendru kasininku skaicius: %d\n", Count);
	else
		fprintf(Output, "\nPrie tokio apkrovimo geresnis procesas 2. Optimalus paprastu ir vip kasininku skaicius: %d %d\n", NormalAccountants, VIPAccountants);
	fprintf(Output, "\nAtejo vidutiniskai paprastu ir vip klientu: %d %d", kiekp, kiekvip);
	fclose(Output);

	return 0;
}

