/*
 * Activity.java
 *
 * Created 12.10.2000
 * Documented 7.7.2001
 */

package timetable.data;

import java.util.Vector;
import timetable.util.*;

/** Reprezentece jedné aktivity v rozvrhovacím problému. 
 * <BR>Má také metody pro přidání a odebrání aktivity z rozvrhu, kontrolu splnění závislostí, zjišťování počtu a seznamu možných umístění v rozvrhu či výpočet počtu kolidujících aktivit při zvolení daného umístění.
 * @author Tomas Muller
 * @version 1.0.1
 */
public class Activity extends java.lang.Object {
    /** Maximální délka zkratky jména.*/
    public static int SHORT_CUT_MAX_LENGTH = 7;
    /** Globální konfigurace rozvrhovače. */
    protected Config config = null;
    
    /** Několika písmená zkratka jména aktivity, pro zobrazení. */
    public String shortCut = null;
    /** Jméno aktivity. */
    public String name = null;    
    /** Délka trvání aktivity. */
    public int length = 1;
    /** Poznámka. */
    public String note = null;
    /** Časové preference aktivity - soft a hard podmínky na jednotlivé časové sloty. */
    protected TimePreference preference = null;
    /** Zdroje, které aktivita může využívat. Jde o množinu skupin zdrojů. */
    public ActivityResources resources = new ActivityResources();
    
    /** Naplánovaný začátek aktivity. Pokud je aktivita nenaplánovaná je hodnota této proměnné -1.*/
    public int start = -1;
    /** Zdroje, které aktivita využívá, je-li naplánovaná. Proměnná musí mít stejnou strukturu, jako proměnná resources.
     * V případě skupiny alternativních zdrojů, zde tato skupina obsahuje pouze jeden zdroj.
     */
    public ActivityResources selectedResources = null;
    
    /** Vstupní rozvrh: začátek aktivity*/
    public int lastStart = -1;
    /** Vstupní rozvrh: požadované zdroje*/
    public ActivityResources lastSelectedResources = null;
    /** Nejlepší nalezený rozvrh: začátek aktivity*/
    public int bestStart = -1;
    /** Nejlepší nalezený rozvrh: požadované zdroje*/
    public ActivityResources bestSelectedResources = null;

    /** Pro plánování, eviduje která aktivita tuto aktivitu naposledy vyhodila z rozvrhu.*/
    public Activity lastRemovedBy = null;
    /** Lze již naplánovanou aktivitu přeplánovat ? (Tedy odebrat z rozvrhu a zařadit na jiné místo.)*/
    public boolean canReschedule = true;
    /** Kolikrát byla aktivita při plánování vyhozena z rozvrhu. Proměnná pomáhá určit nejvíce vadící (nejhůře naplánovatelné) aktivity. Při spuštění řešiče se vždy vynuluje.*/
    public int nrRemoves=0;
    
    /** Konstruktor.
     * @param config konfigurace
     * @param shortCut unikátní zkrácené jmnéno aktivity - pro vizualizaci rozvrhu
     * @param name jméno aktivity
     * @param note poznámka
     * @param length délka trvání aktivity
     */    
    public Activity(Config config,String shortCut,String name,String note,int length) throws TimetableException {
        this.config = config;
        this.name=name;
        this.shortCut=(shortCut==null||shortCut.length()<=SHORT_CUT_MAX_LENGTH?shortCut:shortCut.substring(0,SHORT_CUT_MAX_LENGTH));
        this.note=note;
        this.length=length;
        preference = new TimePreference(config);
    }
    
    /** Konstruktor.
     * @param config konfigurace
     * @param name jméno aktivity
     * @param length délka trvání aktivity
     */    
    public Activity(Config config,String name,int length) throws TimetableException {
        this(config, name, name,null, length);
    }

    /** Konstruktor.
     * @param config konfigurace
     */    
    public Activity(Config config) throws TimetableException {
        this(config, null,null, null, 1);
    }
    
    /** Inicializace pomocných proměnných před dalším plánováním. Automaticky voláno rozvrhovačem. 
     * Jde o vynulování počtu vyhození a aktivyty, která naposled tuto aktivitu vyhodila z rozvrhu 
     */    
    public void reset() {
        nrRemoves=0;
        lastRemovedBy=null;
    }
        
    /** Zjišťuje, zda-li je aktivita již naplánovaná. 
     * @return <CODE>true</CODE> pokud je aktivita naplánovaná
     */    
    public boolean isScheduled() {
        return (start>=0);
    }
    
    /** Vrátí tabulku časových preferencí
     * @return tabulka časových preferencí
     */
    public TimePreference getPreference() {
        return preference;
    }
    
    /** Nastaví tabulku časových preferencí
     * @param preference tabulka časových preferencí
     */
    public void setPreference(TimePreference preference) {
        this.preference=preference;
    }

    /** Odebrání aktivity z rozvrhu. Tedy nastavení začátku na -1 a její odebrání z časových tabulek zdrojů 
     *  @param removedBy aktivita, kvůli které se tato aktivita odebírá
     */    
    public void remove(Activity removedBy) {
        if (start==-1) return;
        for (int i=0;i<selectedResources.size();i++) 
              if (selectedResources.isGroup(i)) {
                  for (int k=0;k<selectedResources.getResources(i).size();k++)
                      for (int j=0;j<length;j++) 
                        selectedResources.getResources(i).get(k).setActivityAt(j+start,null);
                } else 
                    for (int j=0;j<length;j++) 
                      selectedResources.getResource(i).setActivityAt(j+start,null);
        selectedResources.removeAll();
        start=-1;
        nrRemoves++;
        this.lastRemovedBy=removedBy;
    }
        
    /** Naplánování aktivity. Tedy nastavení jejího začátku a propagace alokace jednotlivých časových slotů do časových tabulek vybraných zdrojů. 
     * <BR>Všechny konfliktní aktivity již musí být odebrány !!!
     * @param slot nový začátek aktivity - časový slot
     * @param selectedResources vybrané zdroje
     */    
    public void schedule(int slot, ActivityResources selectedResorces) {
        start = slot;
        this.selectedResources = selectedResorces;
        if (start<0) return;
        for (int i=0;i<selectedResources.size();i++) {
            if (selectedResources.isGroup(i))
                for (int k=0;k<selectedResources.getResources(i).size();k++)
                    for (int j=0;j<length;j++) 
                        selectedResources.getResources(i).get(k).setActivityAt(j+start,this);
            else 
                for (int j=0;j<length;j++) 
                    selectedResources.getResource(i).setActivityAt(j+start,this);
        }
    }
    
    /** Zjišťuje konzistenci naplánování aktivity. Jmenovitě: 
     * <ul><li>Naplánování aktivity nepřesahuje počet možných slotů</li>
     * <li>Naplánování aktivity nepřesahuje daný den</li>
     * <li>Není porušena HARD podmínka v časových preferencích aktivity? </li>
     * <li>Jsou v pořádku časové závislosti, ve kterých je obsažena daná aktivita? </li>
     * <li>Konzistenci vybraných zdrojů s množinou skupin požadovaných zdrojů a jejich správná naalokace</li>
     * <li>Neporušení HARD podmínky v časových preferencích vybraných zdrujů touto aktivitou </li></ul>
     * @param dependencies časové závislosti mezi aktivitami, null pokud nemají být kontrolovány
     * @return <CODE>true</CODE> pokud je vše v pořádku
     */
    public boolean isConsistent(ActivityDependenceGroup dependences) throws TimetableException {
        if (isScheduled()) {
            int slot=this.start;
            if (slot<0 || slot+length>config.getInt(Config.NR_SLOTS)) return false;
            int nrSlotsPerDay = config.getInt(Config.NR_SLOTS)/config.getInt(Config.NR_DAYS);
            if (slot/nrSlotsPerDay != (slot+length-1)/nrSlotsPerDay) return false;
            for (int j=0;j<length;j++) if (preference.isHARD(j+slot)) return false;
            if (dependences!=null && !dependences.isOK(this,slot)) return false;
            for (int i=0;i<selectedResources.size();i++) {
                if (selectedResources.isGroup(i)) {
                    for (int j=0;j<selectedResources.getResources(i).size();j++)
                        if (!selectedResources.getResources(i).get(j).isConsistentFor(this)) return false;
                    if (!resources.getResources(i).haveSameContent(selectedResources.getResources(i))) return false;
                } else {
                    if (!selectedResources.getResource(i).isConsistentFor(this)) return false;
                    if (resources.isGroup(i)) {
                        if (resources.getResources(i).indexOf(selectedResources.getResource(i))<0) return false;
                    } else {
                        if (!resources.getResource(i).equals(selectedResources.getResource(i))) return false;
                    }
                }
            }
        }
        return true;
    }
    
    /** Zjišťuje, zda-li lze aktivitu naplánovat od daného časového slotu. Tedy zda-li existují zdroje, které nemají HARD podmínku v daných slotech. A zdali neexistuje podmínka mezi aktivitami, která by byla porušena a druhá z aktivit by nešla odebrat.
     * @param slot začátek aktivity, pro který je údaj zjišťován
     * @param dependencies časové závislosti mezi aktivitami, null pokud nemají být uvažovány
     * @return <CODE>true</CODE> pokud lze aktivita v daném čase naplánovat.
     */    
    public boolean canScheduleAt(int slot, ActivityDependenceGroup dependences) throws TimetableException {
        if (slot<0 || slot+length>config.getInt(Config.NR_SLOTS)) return false;
        int nrSlotsPerDay = config.getInt(Config.NR_SLOTS)/config.getInt(Config.NR_DAYS);
        if (slot/nrSlotsPerDay != (slot+length-1)/nrSlotsPerDay) return false;
        for (int j=0;j<length;j++) if (preference.isHARD(j+slot)) return false;
        if (dependences!=null && !dependences.isFixedOK(this,slot)) return false;
        return resources.canAllBeFreeAt(slot,length);
    }
    
    /** Zjišťuje, zda-li lze aktivitu naplánovat do daného časového slotu bez kolize. 
     * @param slot začátek aktivity, pro který je údaj zjišťován
     * @param dependencies časové závislosti mezi aktivitami, null pokud nemají být uvažovány
     * @return <CODE>true</CODE> pokud lze aktivita v daném čase naplánovat bez kolize s jinou aktivitou.
     */    
    public boolean canScheduleWithoutCollisionAt(int slot, ActivityDependenceGroup dependences) throws TimetableException {
        if (slot<0 || slot+length>config.getInt(Config.NR_SLOTS)) return false;
        int nrSlotsPerDay = config.getInt(Config.NR_SLOTS)/config.getInt(Config.NR_DAYS);
        if (slot/nrSlotsPerDay != (slot+length-1)/nrSlotsPerDay) return false;
        for (int j=0;j<length;j++) if (preference.isHARD(j+slot)) return false;
        if (dependences!=null && !dependences.isOK(this,slot)) return false;
        return resources.isAllFreeAt(slot,length);
    }
    
    /** Zjišťuje, počet míst, kam lze aktivita v daném čase naplánovat bez kolize. 
     * @param slot začátek aktivity, pro který je údaj zjišťován
     * @param dependencies časové závislosti mezi aktivitami, null pokud nemají být uvažovány
     * @return počet míst, kam lze aktivita v daném čase naplánovat bez kolize s jinou aktivitou.
     */    
    public int countScheduleWithoutCollisionAt(int slot, ActivityDependenceGroup dependences) throws TimetableException {
        if (slot<0 || slot+length>config.getInt(Config.NR_SLOTS)) return 0;
        int nrSlotsPerDay = config.getInt(Config.NR_SLOTS)/config.getInt(Config.NR_DAYS);
        if (slot/nrSlotsPerDay != (slot+length-1)/nrSlotsPerDay) return 0;
        for (int j=0;j<length;j++) if (preference.isHARD(j+slot)) return 0;
        if (dependences!=null && !dependences.isOK(this,slot)) return 0;
        return resources.countAllFreeAt(slot,length);
    }
    
    /** Zjišťuje, počet míst, kam lze aktivita bez kolize naplánovat. (Projde všechny sloty)
     * @param dependencies časové závislosti mezi aktivitami, null pokud nemají být uvažovány
     * @return počet míst, kam lze aktivita naplánovat bez kolize s jinou aktivitou.
     */    
    public int countScheduleWithoutCollision( ActivityDependenceGroup dependences) throws TimetableException {
        int sum=0;
        for (int i=0;i<config.getInt(Config.NR_SLOTS);i++) 
            sum+=countScheduleWithoutCollisionAt(i, dependences);
        return sum;
    }

   /** Zjišťuje počet časových slotů, kam lze aktivita naplánovat (i když spůsobí kolizi).
     * @param dependencies časové závislosti mezi aktivitami, null pokud nemají být uvažovány
     * @return počet slotů, kam lze aktivita naplánovat
    */
    public int countSchedule(ActivityDependenceGroup dependences) throws TimetableException {
        int sum=0;
        for (int i=0;i<config.getInt(Config.NR_SLOTS);i++) 
            if (canScheduleAt(i,dependences)) sum++;
        return sum;
    }
    
    /** Zápis aktivity do objektového streamu. Nutným parametrem je množina všech zdrojů, ukládané zdroje jsou totiž kódovány jako pořadí v této množině. Předchozí umístění a nejlepší nalezené umístění není zapisováno.
     * @param out stream
     * @param alResources množina všech zdrojů
     */
     protected void write(java.io.ObjectOutputStream out, ResourceGroup allResources) throws java.io.IOException {
         out.writeInt(length);
         out.writeInt(start);
         out.writeObject(name);
         out.writeObject(shortCut);
         out.writeObject(note);
         out.writeObject(preference);
         resources.write(out, allResources);
         if (selectedResources==null || start<0) out.writeBoolean(false); else {out.writeBoolean(true); selectedResources.write(out, allResources);};
         out.writeBoolean(canReschedule);
     }
     
     /** Načtení aktivity z streamu objektů.
      * @param in stream
      * @param config globální konfigurace
      * @param allResources množina všech zdrojů
      * @return načtená aktivita
      */
     protected static Activity read(java.io.ObjectInputStream in, Config config, ResourceGroup allResources) throws java.io.IOException, java.lang.ClassNotFoundException, TimetableException {
         Activity a=new Activity(config);
         a.length=in.readInt();
         a.start=in.readInt();
         a.name=(String)in.readObject();
         a.shortCut=(String)in.readObject();
         a.note=(String)in.readObject();
         a.preference=(TimePreference)in.readObject();
         a.preference.config=config;
         a.resources=ActivityResources.read(in,allResources);
         if (in.readBoolean()) a.selectedResources=ActivityResources.read(in,allResources); else a.selectedResources=null;
         a.lastStart=-1;//in.readInt();
         a.lastSelectedResources=null;//(ResourceGroup)in.readObject();
         a.lastRemovedBy=null;//(Aktivita)in.readObject();
         a.canReschedule=in.readBoolean();
         a.nrRemoves=0;
         return a;
     }

    
}
