/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package pnandmd;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import pnandmd.Point2.TworzSiec;

/**
 *
 * @author Ania
 */
public class Simulation {

    private File dir;
    private int step;
    private int type;

    private int[][] wej;
    private int[][] wyj;

    private int[] prio;
    private int[] time;
    private int[] timeBasic;

    private int liczM;
    private int liczT;

    private int ilosc = 0;

    private int typSieci;
    private BufferedReader wyst;

    //numery miejsc ktore byly po kolei odpalane aby nie trzeba bylo liczyc z markingu
    private BufferedWriter numeryMiejsc;

    private int[] marking;

    private List<Integer> enabled = new ArrayList<Integer>();

    private int[] lastFire;

    private boolean markingEnd;

    private BufferedWriter bw;

    public Simulation(File d, int s, int t, boolean markingStep) {
        try {
            this.markingEnd = markingStep;
            dir = d;
            step = s;
            type = t;
            bw = new BufferedWriter(new FileWriter(new File(dir.getAbsolutePath() + File.separator + "markingPN.txt")));
            wyst = new BufferedReader(new FileReader(dir.getAbsolutePath() + File.separator + "wystapienia.txt"));
            numeryMiejsc = new BufferedWriter(new FileWriter(new File(dir.getAbsolutePath() + File.separator + "numeryMiejsc.txt")));
            String linia = wyst.readLine();
            typSieci = Integer.parseInt(linia);
            linia = wyst.readLine();
            String[] tab = linia.split(" ");
            liczT = Integer.parseInt(tab[1]);
            liczM = Integer.parseInt(tab[3]);
            liczT++;
            liczM++;
            marking = new int[liczM];
            if (typSieci == 3) {
                ilosc++;
            } else if ((typSieci == 1) || (typSieci == 2) || (typSieci == 4)) {
                ilosc += Integer.parseInt(tab[5]);
                ilosc /= 2;
            }
            tworzMacierze();
            if (typSieci == 2) {
                lastFire = new int[ilosc];
            }
            if ((type == 1) || (type == 4)) {//siec z czasem
                setTimePrio(0);
            } else if ((type == 2) || (type == 3)) {//siec z priorytetami
                setTimePrio(1);
            }
            setMarking();
        } catch (IOException | NumberFormatException ex) {
            Logger.getLogger(Simulation.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Metoda ktora towrzy macierze wejscia i wyjscia dla sieci
     */
    private void tworzMacierze() {
        try {
            wej = new int[liczM][liczT];
            wyj = new int[liczM][liczT];
            //System.out.println(""+liczM+ " "+liczT);
            String jedna;
            String[] tab;
            String[] tab2;
            int nrT, nrA, nrWyj, nrWej;
            BufferedReader plikT = new BufferedReader(new FileReader("" + dir.getAbsolutePath() + File.separator + "tNazwy.txt"));
            if ((typSieci == 1)) {//punkty
                for (int i = 0; i < liczT; i++) {
                    jedna = plikT.readLine();
                    tab = jedna.split(" ");
                    nrT = Integer.parseInt(tab[1]);
                    nrA = Integer.parseInt(tab[4]);
                    tab[8] = tab[8].replace("p", "");
                    nrWyj = Integer.parseInt(tab[8]);
                    wej[nrA][nrT] = 1;
                    wyj[nrA][nrT] = 1;
                    wyj[nrWyj][nrT] = 1;
                    if (jedna.contains("z")) {
                        tab[12] = tab[12].replace("p", "");
                        nrWej = Integer.parseInt(tab[12]);
                    } else {
                        nrWej = nrA + ilosc;
                    }
                    wej[nrWej][nrT] = 1;
                }
            } else if ((typSieci == 2) || (typSieci == 4)) {
                BufferedReader siec = new BufferedReader(new FileReader("" + dir.getAbsolutePath() + File.separator + "siecAlg1.apnn"));
                while ((jedna = siec.readLine()) != null) {
                    if (jedna.contains("arc")) {
                        tab = jedna.split("from");
                        tab2 = tab[1].split("to");
                        String tab3[] = tab2[1].split("weight");
                        tab2[0] = tab2[0].replace(" \\", "");
                        tab2[0] = tab2[0].replace("{", "");
                        tab2[0] = tab2[0].replace("}", "");
                        tab2[0] = tab2[0].replace("_", "");
                        tab3[0] = tab3[0].replace(" \\", "");
                        tab3[0] = tab3[0].replace("{", "");
                        tab3[0] = tab3[0].replace("}", "");
                        tab3[0] = tab3[0].replace("_", "");
                        if (tab2[0].contains("T")) {//z tranz do miejsca
                            tab2[0] = tab2[0].substring(1);
                            tab3[0] = tab3[0].substring(1);
                            nrT = Integer.parseInt(tab2[0]);
                            nrWyj = Integer.parseInt(tab3[0]);
                            wyj[nrWyj][nrT] = 1;
                        } else {//z miejsca do tranz
                            tab2[0] = tab2[0].substring(1);
                            tab3[0] = tab3[0].substring(1);
                            nrT = Integer.parseInt(tab3[0]);
                            nrWej = Integer.parseInt(tab2[0]);
                            wej[nrWej][nrT] = 1;
                        }
                    }
                }
                siec.close();
            } else if (typSieci == 3) {//konformacja
                for (int i = 0; i < liczT; i++) {
                    jedna = plikT.readLine();
                    tab = jedna.split(" ");
                    tab[4] = tab[4].replace("p", "");
                    tab[7] = tab[7].replace("p", "");
                    nrWej = Integer.parseInt(tab[4]);
                    nrWyj = Integer.parseInt(tab[7]);
                    wyj[nrWyj][i] = 1;
                    wej[nrWej][i] = 1;
                }
            }
            plikT.close();
        } catch (IOException ex) {
            Logger.getLogger(TworzSiec.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Metoda ktora wypelnia tablice czasow lub priorytetow w zaleznosci od mode
     *
     * @param mode 1 - tablica priorytetow, 0 - tablica czasow
     */
    private void setTimePrio(int mode) {
        try {
            BufferedReader tt = null;
            if (mode == 1) {
                prio = new int[liczT];
            } else {
                time = new int[liczT];
                timeBasic = new int[liczT];
                tt = new BufferedReader(new FileReader(dir.getAbsolutePath() + File.separator + "czasy.txt"));
            }
            String linia;
            String tab[];
            for (int i = 0; i < liczT; i++) {
                if (mode == 1) {
                    linia = wyst.readLine();
                } else {
                    linia = tt.readLine();
                }
                tab = linia.split(":");
                tab[1] = tab[1].replace(" ", "");
                if (mode == 1) {
                    prio[i] = Integer.parseInt(tab[1]);
                } else {
                    time[i] = Integer.parseInt(tab[1]);
                    timeBasic[i] = Integer.parseInt(tab[1]);
                }
            }
            if (mode == 1) {
                wyst.close();
            } else {
                tt.close();
            }
        } catch (IOException ex) {
            Logger.getLogger(Simulation.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Metoda ktora odczytuje marking z pliku.
     */
    private void setMarking() {
        try {
            BufferedReader siec = new BufferedReader(new FileReader(dir.getAbsolutePath() + File.separator + "siecAlg1.apnn"));
            String linia;
            String tab[];
            int i = 0;
            while ((linia = siec.readLine()) != null) {
                if (linia.contains("place")) {
                    tab = linia.split("init");
                    tab[1] = tab[1].replace("{", "");
                    tab[1] = tab[1].replace("}", "");
                    marking[i] = Integer.parseInt(tab[1]);
                    i++;
                }
            }
            siec.close();
        } catch (IOException ex) {
            Logger.getLogger(Simulation.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Metoda wykonujaca step krokow symulacji w zaleznosc od danego typu sieci.
     */
    public void simulate() {
        try {
            int tr;
            int zapCzas = 0;
            int clockState = 0;
            Random rn = new Random();
            Random zmlos = new Random();
            ArrayList oldEnabled = new ArrayList();
            List<Integer> firable = new ArrayList<Integer>();

            //scaleTimes();
            for (int i = 0; i < step; i++) {//tyle krokow ile zadane
                //znajdz umozliwione
                findEnabled();
                tr = -1;
                //odpalanie
                if (enabled.size() > 0) {

                    /*System.out.println("umozliwione");
                     for(int mm = 0; mm < enabled.size(); mm++){
                     System.out.println((int)enabled.get(mm));
                     }*/
                    if (type == 0) {//zwykle
                        tr = (int) enabled.get(rn.nextInt(enabled.size()));
                    } else if (type == 1) {//z czasem
                        //ustalenei nowych czasow po odpaleniu poprzedniej tranz
                        //dla tych ktore byly juz umozliwione odjecie od ich czasow czasu odpalonej
                        //dla tych ktore nowo umozliwione to ustawienie pierwotnych czasow
                        int n = enabled.size();
                        /*for (int j = 0; j < n; j++) {
                         if ((!oldEnabled.contains((int) enabled.get(j))) || ((int) enabled.get(j) == tr)) {
                         time[(int) enabled.get(j)] = timeBasic[(int) enabled.get(j)];
                         } else {
                         time[(int) enabled.get(j)] -= zapCzas;
                         }
                         }*/
                        //znalezienie tranz z min czasem do odpalenia
                        int min = time[(int) enabled.get(0)];
                        int minNr = (int) enabled.get(0);

                        for (int j = 1; j < n; j++) {
                            if (min > time[(int) enabled.get(j)]) {
                                min = time[(int) enabled.get(j)];
                                minNr = (int) enabled.get(j);
                            }
                        }
                        tr = minNr;
                        zapCzas = min;
                        oldEnabled = new ArrayList(enabled);
                        for (int j = 0; j < n; j++) {
                            time[(int) enabled.get(j)] -= zapCzas;
                            if (time[(int) enabled.get(j)] < 0) {
                                time[(int) enabled.get(j)] = 0;
                            }
                        }
                        time[tr] = timeBasic[tr];
                        //System.out.println("min: " + zapCzas + " minNr " + tr + " czas bazowy " + time[tr]);
                    } else if (type == 4) {//z czasem moje
                        if (i % ilosc == 0) {
                            clockState++;
                        }
                        firable.clear();
                        for (Integer trNum : enabled) {
                            if (time[trNum] <= clockState) {
                                firable.add(trNum);
                            }
                        }
                        tr = (int) firable.get(rn.nextInt(firable.size()));
                    } else if (type == 2) {//priorytety
                        tr = (int) enabled.get(rn.nextInt(enabled.size()));
                        ArrayList conf = findConflicts(tr);
                        int max = prio[(int) conf.get(0)];
                        int maxNr = (int) conf.get(0);
                        int n = conf.size();
                        for (int j = 1; j < n; j++) {
                            if (max < prio[(int) conf.get(j)]) {
                                max = prio[(int) conf.get(j)];
                                maxNr = (int) conf.get(j);
                            }
                        }
                        tr = maxNr;
                    } else if (type == 3) {//losowe priorytety
                        tr = (int) enabled.get(rn.nextInt(enabled.size()));
                        ArrayList conf = findConflicts(tr);
                        int skonf = 0;
                        int n = conf.size();
                        for (int j = 0; j < n; j++) {
                            skonf += prio[(int) conf.get(j)];
                        }
                        int wylosowana = zmlos.nextInt(skonf + 1);
                        int pom = 0;
                        for (int j = 0; j < n; j++) {
                            pom += prio[(int) conf.get(j)];
                            if (pom >= wylosowana) {
                                tr = (int) conf.get(j);
                                break;
                            }
                        }
                    }
                    //System.out.println("fire "+tr);
                    fire(tr);
                    if (typSieci == 2) {
                        int nrM = 0;
                        for (int j = 0; j < ilosc; j++) {
                            if (wej[j][tr] == 1) {
                                nrM = j;
                                break;
                            }
                        }
                        lastFire[nrM] = tr;
                    }
                }
                if (!markingEnd) {
                    zapiszMarking();
                    numeryMiejsc.write("tranz: " + tr);
                    numeryMiejsc.newLine();
                    bw.write("END");
                    bw.newLine();
                }
            }
            if (markingEnd) {
                zapiszMarking();
            }
            bw.close();
            numeryMiejsc.close();
        } catch (IOException ex) {
            Logger.getLogger(Simulation.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void scaleTimes() {
        int maxTime = 0;
        for (Integer tranzTime : time) {
            if (tranzTime > maxTime) {
                maxTime = tranzTime;
            }
        }
        int n = time.length;
        for (int i = 0; i < n; i++) {
            time[i] = time[i] * step / maxTime;
        }
    }

    /**
     * Metoda ktora zwraca liste tranzycji umozliwionych i bedacych w konflickie
     * z tr
     *
     * @param tr nr tranz z ktora sprawdza sie konflikty
     * @return
     */
    private ArrayList findConflicts(int tr) {
        ArrayList conf = new ArrayList();
        int n = enabled.size();
        int nr;
        for (int j = 0; j < liczM; j++) {
            if (wej[j][tr] == 1) {//j to nr miejsce wej dla tranz tr
                for (int i = 0; i < n; i++) {//wszystkie tranz z enabled
                    nr = (int) enabled.get(i);
                    if (wej[j][nr] == 1) {//jesli tez maja miejsce wejsciowe j-te
                        conf.add(nr);//to dodaj do tych w konflikcie
                    }

                }
            }
        }
        return conf;
    }

    private void fire(int tr) {
        for (int i = 0; i < liczM; i++) {
            if (wej[i][tr] == 1) {
                marking[i] -= 1;
            }
            if (wyj[i][tr] == 1) {
                marking[i] += 1;
            }
        }
    }

    /**
     * Wstawienie umozliwionych tranzycji do listy.
     */
    private void findEnabled() {
        boolean ena;
        int k;
        enabled.clear();
        //spr ktore tranzycje sa umozliwione - pierwszy raz spr wszystkie, pozniej tylko te nowe  
        for (int j = 0; j < liczT; j++) {
            //j - numer tranzycji
            ena = true;
            k = 0;//numer miejsca
            while ((ena) && (k < liczM)) {
                if (wej[k][j] > marking[k]) {
                    ena = false;
                }
                k++;
            }
            if (ena) {
                enabled.add(j);
            }
        }

    }

    public int[] zwrocMarking() {
        return marking;
    }

    public void wypiszMarking() {
        int n = marking.length;
        System.out.println("marking:");
        for (int i = 0; i < n; i++) {
            if (marking[i] != 0) {
                System.out.println("miejsce " + i + " : " + marking[i]);
            }
        }
    }

    public void wypiszMarkingAll() {
        int n = marking.length;
        System.out.println("marking:");
        for (int i = 0; i < n; i++) {
            System.out.println("miejsce " + i + " : " + marking[i]);
        }
    }

    public void zapiszMarking() {
        int n = marking.length;
        try {
            for (int i = 0; i < n; i++) {
                bw.write("" + marking[i] + " ");
                /*if (marking[i] != 0) {
                 numeryMiejsc.write("" + i);
                 numeryMiejsc.newLine();
                 }*/
            }
            bw.newLine();
            if (typSieci == 2) {
                for (int i = 0; i < ilosc; i++) {
                    bw.write("" + lastFire[i] + " ");
                }
                bw.newLine();
            }

        } catch (IOException ex) {
            Logger.getLogger(Simulation.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

}
