/*******************************************************************************
 * Copyright (C) 2013, 2015 Tuukka Ruhanen
 *
 * JSana is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * JSana is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with JSana; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *******************************************************************************/
package tietorakenteet;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * <pre>
 * Esivalintojen käsittelijäluokka.
 * 
 * Vastuualueet:
 * - uuden esivalinnan lisääminen
 * - esivalintojen tallentaminen tiedostoon
 * - esivalintojen järjesteleminen
 * </pre>
 * @author Tuukka R.
 * @version Mar 8, 2012
 * @example
 * <pre name="test">
 * Esivalinnat evt = new Esivalinnat();
 * Esivalinta ev = new Esivalinta("1.Moos.1:1");
 * Esivalinta ev2 = new Esivalinta("1.Moos.1:2");
 * 
 * evt.lisaa(ev);
 * evt.lisaa(ev2);
 * evt.anna(0).equals(ev) === true;
 * 
 * evt.poista(0);
 * evt.anna(0).equals(ev2) === true;
 * evt.getLkm() === 1;
 * evt.lisaa(ev);
 * evt.siirraYlosAlas(0, 1);
 * evt.anna(0).equals(ev) === true;
 * for (Esivalinta eva : evt) // iteraattorin testaus
 *     evt.getLkm() === 2;
 * </pre>
 */
public class Esivalinnat implements Iterable<Esivalinta> {

    private static final int MAX_LKM = 10;
    private final String TIEDOSTOPAATE = "sch";
    private int lkm = 0;
    private Esivalinta[] alkiot = new Esivalinta[MAX_LKM];
    private File tiedosto;
    private boolean muutettu = false;

    /**
     * Oletusmuodostaja
     */
    public Esivalinnat() {
        // attribuuttien oma alustus riittää
    }

    /**
     * Uuden esivalinnan lisääminen. Esivalintataulukon kokoa kasvatetaan tarvittaessa.
     * @param eV lisättävä esivalinta
     */
    public void lisaa(Esivalinta eV) {
        if (lkm >= alkiot.length) {
            Esivalinta[] alkiotOld = alkiot;
            alkiot = new Esivalinta[lkm + 10];
            for (int i = 0; i < lkm; i++) {
                alkiot[i] = alkiotOld[i];
            }
        }
        muutettu = true;
        alkiot[lkm++] = eV;
    }

    /**
     * Korvaa paikassa i olevan esivalinnan eV:llä.
     * @param i
     * @param eV
     */
    public void replace(int i, Esivalinta eV) {
        alkiot[i] = eV;
    }

    /**
     * Poistaa indeksissä i olevan esivalinnan
     * @param i indeksin numero, jota vastaava esivalinta poistetaan
     */
    public void poista(int i) {
        // tarkistetaan järkevyys TODO Errorit?
        if (i < 0 || i >= lkm)
            return;
        muutettu = true;
        lkm--;
        for (int j = i; j < lkm; j++)
            alkiot[j] = alkiot[j + 1];
        alkiot[lkm] = null;
    }

    /**
     * Poistaa kaikki esivalinnat (laittaa nulleiksi)
     */
    public void tyhjenna() {
        if (lkm == 0)
            return;
        muutettu = true;
        for (int j = 0; j < lkm; j++)
            alkiot[j] = null;
        lkm = 0;
    }

    /**
     * Antaa halutun Esivalinta-olion
     * @param i halutun esivalinnan indeksi Esivalinnat-tietorakenteessa
     * @return indeksiä i vastaava Esivalinta-olio
     */
    public Esivalinta anna(int i) {
        if (i < 0 || lkm <= i)
            return null;
        return alkiot[i];
    }

    // public void setNaytetty(int index, boolean naytetty) {
    // alkiot[index].setNaytetty(naytetty);
    // }

    // public void nollaa() {
    // for (Esivalinta eV : this) {
    // eV.setNaytetty(false);
    // }
    // }

    /**
     * Palauttaa luotujen esivalintojen lukumäärän
     * @return esivalintojen lukumäärä
     */
    public int getLkm() {
        return lkm;
    }

    /**
     * Esivalinnan siirtäminen tietorakenteessa yläs tai alas.
     * Käytännässä vaihtaa kahden alkion järjestystä ArrayListissä
     * @param i siirrettävän esivalinnan indeksi
     * @param suunta arvolla 1 alaspäin, arvolla -1 yläspäin
     * @return the resulting index, -1 if nothing was done
     */
    public int siirraYlosAlas(int i, int suunta) {
        // ensin tarkistetaan järkevyys TODO Errorit?
        if (i < 0 || i >= alkiot.length || Math.abs(suunta) != 1 || i + suunta == -1 || i + suunta == getLkm())
            return -1;
        muutettu = true;
        Esivalinta eVTemp = alkiot[i + suunta];
        alkiot[i + suunta] = alkiot[i];
        alkiot[i] = eVTemp;
        return i + suunta;
    }

    /**
     * Iteraattori, joka käy luodut esivalinnat läpi
     * @return itse tehty iteraattori
     */
    @Override
    public Iterator<Esivalinta> iterator() {
        return new EsivalinnatIterator();
    }

    /**
     * Oma iteraattori esivalinnoille
     * @author Tuukka Ruhanen
     * @version Mar 12, 2012
     */
    public class EsivalinnatIterator implements Iterator<Esivalinta> {
        private int kohdalla = -1;

        @Override
        public boolean hasNext() {
            return kohdalla + 1 < lkm;
        }

        @Override
        public Esivalinta next() throws NoSuchElementException {
            if (!hasNext())
                throw new NoSuchElementException("Ei ole enempää alkioita.");
            kohdalla++;
            return alkiot[kohdalla];
        }

        // poistamista ei tueta
        @Override
        public void remove() throws UnsupportedOperationException {
            throw new UnsupportedOperationException("Poistamista ei tueta.");
        }
    }

    /**
     * Esivalintatiedoston sisällön lukeminen esivalinnoiksi
     * @param tiedostoTemp luettava tiedosto; luetaan ensimmäinen juurihakemistosta löytyvä tiedosto, jos null
     */
    public void lueTiedostosta(File tiedostoTemp) {
        if (tiedostoTemp == null || !tiedostoTemp.exists()) {
            tiedosto = lueEnsimmainen();
        } else
            tiedosto = tiedostoTemp;
        if (tiedosto == null || tiedosto.getName().length() == 0)
            return;

        this.tyhjenna();
        BufferedReader in = null;

        try {
            in = new BufferedReader(new FileReader(tiedosto));
            String rivi;
            while ((rivi = in.readLine()) != null) {
                // TODO Heittämään omaa erroria?
                Esivalinta eV = new Esivalinta(rivi);
                lisaa(eV);
            }
        } catch (FileNotFoundException ex) {
            System.err.println("Input-tiedostoa ei läytynyt: " + ex.getMessage());
        } catch (IOException ex) {
            System.err.println("Input-tiedoston sisällässä on jotain vikaa: " + ex.getMessage());
        } finally {
            try {
                if (in != null)
                    in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Hakee juurihakemistosta esivalintatiedostoa
     * @return löytynyt esivalintatiedosto; null, jos ei löytynyt
     */
    private File lueEnsimmainen() {
        try {
            String jarPath = Esivalinnat.class.getProtectionDomain().getCodeSource().getLocation().getPath();
            jarPath = jarPath.substring(0, jarPath.lastIndexOf("/") + 1);
            jarPath = URLDecoder.decode(jarPath, "UTF-8");
            File[] filesWithJar = new File(jarPath).listFiles();

            String workingPath = "./";
            File[] filesWorking = new File(workingPath).listFiles();

            File[] bothFiles = new File[filesWithJar.length + filesWorking.length];
            System.arraycopy(filesWithJar, 0, bothFiles, 0, filesWithJar.length);
            System.arraycopy(filesWorking, 0, bothFiles, filesWithJar.length, filesWorking.length);

            if (bothFiles[0] == null)
                return null;
            for (File f : bothFiles) {
                String name = f.getName();
                int i = name.lastIndexOf('.');
                String ext = null;
                if (i > 0 && i < name.length() - 1) {
                    ext = name.substring(i + 1).toLowerCase();
                }
                if (ext != null && ext.equals(TIEDOSTOPAATE)) {
                    return f;
                }
            }
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Esivalintojen tallentaminen tekstitiedostoksi
     * @param tiedostoTemp tallennettava tiedosto; tallennetaan alkuperäiseen, jos null. Mikäli kaikki esivalinnat on poistettu, tiedostokin poistetaan
     * @return tallennetun tiedoston canonicalpath (null jos tiedostoa ei löydy tai sattuu virhe)
     */
    public String tallennaTiedostoon(File tiedostoTemp) {
        if (tiedostoTemp != null)
            tiedosto = tiedostoTemp;
        else {
            if (tiedosto == null)
                return null; // pitää luoda uusi tiedosto ("tallenna nimellä")
            if (!muutettu)
                return tiedosto.getAbsolutePath(); // ei suoriteta toimenpiteitä
        }

        tiedosto.delete();
        if (getLkm() == 0) // jos tietorakenne on tyhjä, suoritetaan ainoastaan tiedoston poisto
            return tiedosto.getAbsolutePath();
        PrintStream out = null;

        try {
            out = new PrintStream(new FileOutputStream(tiedosto));
            for (Esivalinta eV : this)
                out.println(eV);
        } catch (FileNotFoundException ex) {
            System.err.println("Esivalintatiedosto ei aukea: " + ex.getMessage());
        } finally {
            if (out != null)
                out.close();
        }

        muutettu = false;
        return tiedosto.getAbsolutePath();
    }
}
