package adecWatt.model;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.TreeMap;
import java.util.TreeSet;

import misc.Log;
import misc.Util;
import static misc.Config.FS;

import static adecWatt.model.PermanentDB.backExtention;
import static adecWatt.model.PermanentDB.dataDir;
import static adecWatt.model.PermanentDB.localRemoteDir;
import static adecWatt.model.PermanentDB.localToken;
import static adecWatt.model.PermanentDB.serverToken;

public class TagDB {

    // ========================================
    static public final String lpiExtention = "lpi";
    static public final File[] getLpiFiles (File dir) {
	return dir.listFiles (new FileFilter () {
		public boolean accept (File file) {
		    try {
			return file.isFile () && !file.isHidden () && lpiExtention.equals (Util.getExtention (file).toLowerCase ());
		    } catch (Exception e) {
			return false;
		    }
		}
	    });
    }

    // ========================================
    private TreeSet<String> words = new TreeSet<String> ();
    private String dir;
    public ImgTag imgTag = new ImgTag ();
    protected ImgTag [] localRemoteTag = {
	new ImgTag (),
	new ImgTag ()
    };

    // ========================================
    public TagDB (String dir) {
	this.dir = dir;
    }
    public void reload () {
	words.clear ();
	imgTag.clear ();
	try {
	    for (int i = 0; i < localRemoteDir.size (); i++) {
		localRemoteTag [i].clear ();
		File tmpDir = new File (dataDir, localRemoteDir.get (i)+FS+dir);
		if (!tmpDir.isDirectory ())
		    continue;
		for (File file : getLpiFiles (tmpDir))
		    localRemoteTag[i].readLpi (file);
	    }
	    localRemoteTag[0].clean (localRemoteTag[1]);
	    imgTag.merge (localRemoteTag[1]);
	    imgTag.merge (localRemoteTag[0]);
	} catch (Exception e) {
	    Log.keepLastException ("ImageDB::reload", e);
	}
    }

    // ========================================
    public Collection<String> getTags (String imageName) {
	return imgTag.getTags (imageName);
    }

    public void putTags (String imageName, Collection<String> imageTags) {
	if (!imgTag.addTags (imageName, imageTags))
	    return;
	try {
	    if ((new TreeSet<String> (imageTags)).equals (localRemoteTag[1].getTags (imageName)))
		localRemoteTag [0].addTags (imageName, null);
	    else
		localRemoteTag [0].addTags (imageName, imageTags);
	    localRemoteTag [0].writeLpi (localToken, User.splitId (imageName)[0]);
	} catch (Exception e) {
	    Log.keepLastException ("ImageDB::putTags", e);
	}
    }

    public void promote (Collection<String> imageNames) {
	if (imageNames == null)
	    return;
	TreeSet<Integer> ownerIds = new TreeSet<Integer> ();
	for (String imageName : imageNames) {
	    Collection<String> imageTags = localRemoteTag [0].getTags (imageName);
	    if (imageTags == null)
		continue;
	    localRemoteTag [1].addTags (imageName, imageTags);
	    localRemoteTag [0].addTags (imageName, null);
	    ownerIds.add (User.splitId (imageName)[0]);
	}
	for (int ownerId : ownerIds) {
	    localRemoteTag [1].writeLpi (serverToken, ownerId);
	    localRemoteTag [0].writeLpi (localToken, ownerId);
	}
    }

    public void renameImages (TreeMap<String, String> translateMap) {
	for (int i = 0; i < localRemoteDir.size (); i++) {
	    TreeSet<Integer> ownerIds = new TreeSet<Integer> ();
	    for (String oldImageName : translateMap.keySet ()) {
		Collection<String> imageTags = localRemoteTag [i].getTags (oldImageName);
		if (imageTags == null)
		    continue;
		String newImageName = translateMap.get (oldImageName);
		localRemoteTag [i].addTags (newImageName, imageTags);
		localRemoteTag [i].addTags (oldImageName, null);
		ownerIds.add (User.splitId (oldImageName)[0]);
		ownerIds.add (User.splitId (newImageName)[0]);
	    }
	    for (int ownerId : ownerIds)
		localRemoteTag [i].writeLpi (localRemoteDir.get (i), ownerId);
	}
    }

    // ========================================
    public TreeSet<String> cloneImageTags (Collection<String> imageTags) {
	TreeSet<String> result = new TreeSet<String> ();
	for (String word : imageTags)
	    result.add (getWord (word));
	return result;
    }
    public String getWord (String word) {
	if (word == null)
	    return null;
	word = word.toLowerCase ();
	String floor = words.floor (word);
	if (floor != null && floor.equals (word))
	    return floor;
	words.add (word);
	return word;
    }

    // ========================================
    public class ImgTag {
	// ----------------------------------------
	TreeMap<Integer, TreeMap<Integer, TreeSet<String>>> ids = new TreeMap<Integer, TreeMap<Integer, TreeSet<String>>> ();

	// ----------------------------------------
	public int size () { return ids.size (); }

	public void clear () {
	    ids.clear ();
	}

	// ----------------------------------------
	public void merge (ImgTag others) {
	    for (int ownerId : others.ids.keySet ()) {
		TreeMap<Integer, TreeSet<String>> refImg = others.ids.get (ownerId);
		TreeMap<Integer, TreeSet<String>> idsImg = ids.get (ownerId);
		if (idsImg == null)
		    ids.put (ownerId, idsImg = new TreeMap<Integer, TreeSet<String>> ());
		for (int imageId : refImg.keySet ())
		    idsImg.put (imageId, cloneImageTags (refImg.get (imageId)));
	    }
	}

	// ----------------------------------------
	public void clean (ImgTag ref) {
	    for (int ownerId : ids.keySet ()) {
		TreeMap<Integer, TreeSet<String>> refImg = ref.get (ownerId);
		if (refImg == null)
		    continue;
		TreeMap<Integer, TreeSet<String>> idsImg = ids.get (ownerId);
		TreeSet<Integer> imgToRemove = new TreeSet<Integer> ();
		for (int imageName : idsImg.keySet ()) {
		    TreeSet<String> refNames = refImg.get (imageName);
		    if (refNames == null)
			continue;
		    if (refNames.equals (idsImg.get (imageName)))
			imgToRemove.add (imageName);
		}
		if (imgToRemove.size () > 0) {
		    for (int imageName : imgToRemove)
			idsImg.remove (imageName);
		    writeLpi (localToken, ownerId);
		}
	    }
	}

	// ----------------------------------------
	public void addTags (String line) {
	    if (line == null || line.startsWith ("#") || line.isEmpty ())
		return;
	    int sepIdx = line.indexOf (":");
	    String imageName = line.substring (0, sepIdx);
	    String[] arg = line.substring (sepIdx+1).split (":");
	    addTags (imageName, Arrays.asList (arg));
	}

	// ----------------------------------------
	public boolean addTags (String imageName, Collection<String> imageTags) {
	    int [] w = User.splitId (imageName);
	    int ownerId = w[0];
	    int imgId = w[1];
	    TreeSet<String> oldValues = null;
	    try {
		oldValues = ids.get (ownerId).get (imgId);
	    } catch (Exception e) {
	    }
	    if (imageTags == null || imageTags.size () == 0) {
		if (oldValues == null || oldValues.size () == 0)
		    return false;
		TreeMap<Integer, TreeSet<String>> custumBD = ids.get (ownerId);
		custumBD.remove (imgId);
		if (custumBD.size () == 0)
		    ids.remove (ownerId);
		return true;
	    }
	    if (imageTags.equals (oldValues))
		return false;
	    TreeMap<Integer, TreeSet<String>> custumBD = ids.get (ownerId);
	    if (custumBD == null)
		ids.put (ownerId, custumBD = new TreeMap<Integer, TreeSet<String>> ());
	    custumBD.put (imgId, cloneImageTags (imageTags));
	    return true;
	}

	// ----------------------------------------
	public TreeMap<Integer, TreeSet<String>> get (int ownerId) {
	    try {
		return ids.get (ownerId);
	    } catch (Exception e) {
		return null;
	    }
	}

	// ----------------------------------------
	public Collection<String> getTags (String imageName) {
	    try {
		int [] w = User.splitId (imageName);
		return ids.get (w[0]).get (w[1]);
	    } catch (Exception e) {
		return null;
	    }
	}

	// ----------------------------------------
	public void readLpi (File file) {
	    int lineNb = 0;
	    String line = null;
	    BufferedReader in = null;
	    try {
		in = new BufferedReader (new InputStreamReader (new FileInputStream (file)));
		for (;;) {
		    lineNb++;
		    line = in.readLine ();
		    if (line == null)
			break;
		    addTags (line);
		}
	    } catch (Exception e) {
		Log.keepLastException ("ImageDB::readLpi: "+file+":"+lineNb+" <"+line+">", e);
	    } finally {
		try {
		    in.close ();
		} catch (Exception e) {
		}
	    }
	}

	// ----------------------------------------
	public void writeLpi (String placeToken, int ownerId) {
	    File file = new File (dataDir, String.format ("%s%03d.%s", placeToken+FS+dir+FS, ownerId, lpiExtention));
	    Util.backup (file, lpiExtention, backExtention);
	    TreeMap<Integer, TreeSet<String>> custumBD = get (ownerId);
	    if (custumBD == null || custumBD.size () < 1)
		return;
	    file.getParentFile ().mkdirs ();
	    file.setExecutable (false);
	    PrintWriter out = null;
	    try {
		out = new PrintWriter (new FileWriter (file));
		for (int imageId : custumBD.keySet ()) {
		    TreeSet<String> imageTags = custumBD.get (imageId);
		    if (imageTags.size () < 1)
			continue;
		    out.print (User.getDataId (ownerId, imageId));
		    for (String tag : imageTags)
			out.print (":"+tag);
		    out.println ();
		}
	    } catch (Exception e) {
		Log.keepLastException ("ImageDB::writeLpi", e);
	    } finally {
		try {
		    out.close ();
		} catch (Exception e) {
		}
	    }
	}

	// ----------------------------------------
    }

    // ========================================
}
