/*
 * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
 *     
 * This file is part of KeePass DX.
 *
 *  KeePass DX is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  KeePass DX 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 KeePass DX.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package com.kunzisoft.keepass.database;

import com.kunzisoft.keepass.database.security.ProtectedBinary;
import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.utils.SprEngineV4;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {

	public static final String STR_TITLE = "Title";
	public static final String STR_USERNAME = "UserName";
	public static final String STR_PASSWORD = "Password";
	public static final String STR_URL = "URL";
	public static final String STR_NOTES = "Notes";

	// To decode each field not serializable
    private transient PwDatabaseV4 mDatabase = null;
    private transient boolean mDecodeRef = false;

	private PwIconCustom customIcon = PwIconCustom.ZERO;
    private long usageCount = 0;
    private PwDate parentGroupLastMod = new PwDate();
    private Map<String, String> customData = new HashMap<>();

    private ExtraFields fields = new ExtraFields();
    private HashMap<String, ProtectedBinary> binaries = new HashMap<>();
	private String foregroundColor = "";
	private String backgroupColor = "";
	private String overrideURL = "";
	private AutoType autoType = new AutoType();
	private ArrayList<PwEntryV4> history = new ArrayList<>();

	private String url = "";
	private String additional = "";
	private String tags = "";

	public PwEntryV4() {
	    super();
	}
	
	public PwEntryV4(PwGroupV4 p) {
		construct(p);
	}

    public void updateWith(PwEntryV4 source) {
        super.assign(source);
        customIcon = source.customIcon;
        usageCount = source.usageCount;
        parentGroupLastMod = source.parentGroupLastMod;
        // TODO customData

        fields = source.fields;
        binaries = source.binaries;
        foregroundColor = source.foregroundColor;
        backgroupColor = source.backgroupColor;
        overrideURL = source.overrideURL;
        autoType = source.autoType;
        history = source.history;

        url = source.url;
        additional = source.additional;
        tags = source.tags;
    }

    @SuppressWarnings("unchecked")
    @Override
    public PwEntryV4 clone() {
        PwEntryV4 newEntry = (PwEntryV4) super.clone();

        // Attributes in parent
        addCloneAttributesToNewEntry(newEntry);

        // Attributes here
        newEntry.customIcon = new PwIconCustom(this.customIcon);
        // newEntry.usageCount stay the same in copy
        newEntry.parentGroupLastMod = this.parentGroupLastMod.clone();

        newEntry.fields = this.fields.clone();
        // TODO customData make copy from hashmap
        newEntry.binaries = (HashMap<String, ProtectedBinary>) this.binaries.clone();
        // newEntry.foregroundColor stay the same in copy
        // newEntry.backgroupColor stay the same in copy
        // newEntry.overrideURL stay the same in copy
        newEntry.autoType = autoType.clone();
        newEntry.history = (ArrayList<PwEntryV4>) this.history.clone();

        // newEntry.url stay the same in copy
        // newEntry.additional stay the same in copy
        // newEntry.tags stay the same in copy

        return newEntry;
    }

	@Override
	public void startToManageFieldReferences(PwDatabase db) {
        this.mDatabase = (PwDatabaseV4) db;
        this.mDecodeRef = true;
	}

	@Override
	public void endToManageFieldReferences() {
        this.mDatabase = null;
        this.mDecodeRef = false;
	}
	
	private String decodeRefKey(boolean decodeRef, String key) {
		String text = getProtectedStringValue(key);
		if (decodeRef) {
			text = decodeRef(text, mDatabase);
		}
		return text;
	}

	private String decodeRef(String text, PwDatabase db) {
		if (db == null) { return text; }
		SprEngineV4 spr = new SprEngineV4();
		return spr.compile(text, this, db);
	}

	@Override
	public String getUsername() {
		return decodeRefKey(mDecodeRef, STR_USERNAME);
	}

	@Override
	public String getTitle() {
		return decodeRefKey(mDecodeRef, STR_TITLE);
	}
	
	@Override
	public String getPassword() {
		return decodeRefKey(mDecodeRef, STR_PASSWORD);
	}

	@Override
	public void setTitle(String title) {
		PwDatabaseV4 db = mDatabase;
		boolean protect = db.getMemoryProtection().protectTitle;
		
		setProtectedString(STR_TITLE, title, protect);
	}

	@Override
	public void setUsername(String user) {
		PwDatabaseV4 db = mDatabase;
		boolean protect = db.getMemoryProtection().protectUserName;
		
		setProtectedString(STR_USERNAME, user, protect);
	}

	@Override
	public void setPassword(String pass) {
		PwDatabaseV4 db = mDatabase;
		boolean protect = db.getMemoryProtection().protectPassword;
		
		setProtectedString(STR_PASSWORD, pass, protect);
	}

	@Override
	public void setUrl(String url) {
		PwDatabaseV4 db = mDatabase;
		boolean protect = db.getMemoryProtection().protectUrl;
		
		setProtectedString(STR_URL, url, protect);
	}

	@Override
	public void setNotes(String notes) {
		PwDatabaseV4 db = mDatabase;
		boolean protect = db.getMemoryProtection().protectNotes;
		
		setProtectedString(STR_NOTES, notes, protect);
	}

	public String getProtectedStringValue(String key) {
		return fields.getProtectedStringValue(key);
	}

	public void setProtectedString(String key, String value, boolean protect) {
		fields.putProtectedString(key, value, protect);
	}

	public PwIconCustom getCustomIcon() {
	    return customIcon;
    }

    public void setCustomIcon(PwIconCustom icon) {
	    this.customIcon = icon;
    }

	public PwDate getLocationChanged() {
		return parentGroupLastMod;
	}

	public long getUsageCount() {
		return usageCount;
	}

	public void setLocationChanged(PwDate date) {
		parentGroupLastMod = date;
	}

	public void setUsageCount(long count) {
		usageCount = count;
	}

	@Override
	public String getNotes() {
		return decodeRefKey(mDecodeRef, STR_NOTES);
	}

	@Override
	public String getUrl() {
		return decodeRefKey(mDecodeRef, STR_URL);
	}

	@Override
	public PwIcon getIcon() {
		if (customIcon == null || customIcon.uuid.equals(PwDatabase.UUID_ZERO)) {
			return super.getIcon();
		} else {
			return customIcon;
		}
	}

	@Override
	public boolean allowExtraFields() {
		return true;
	}

	public ExtraFields getFields() {
	    return fields;
    }

	public void addExtraField(String label, ProtectedString value) {
	    fields.putProtectedString(label, value);
    }

    @Override
    public void removeAllCustomFields() {
        fields.removeAllCustomFields();
    }

    public HashMap<String, ProtectedBinary> getBinaries() {
        return binaries;
    }

    public void putProtectedBinary(String key, ProtectedBinary value) {
	    binaries.put(key, value);
    }

    public String getForegroundColor() {
	    return foregroundColor;
    }

    public void setForegroundColor(String color) {
        this.foregroundColor = color;
    }

    public String getBackgroupColor() {
        return backgroupColor;
    }

    public void setBackgroupColor(String color) {
        this.backgroupColor = color;
    }

    public String getOverrideURL() {
        return overrideURL;
    }

    public void setOverrideURL(String overrideURL) {
        this.overrideURL = overrideURL;
    }

    public AutoType getAutoType() {
        return autoType;
    }

    public void setAutoType(AutoType autoType) {
        this.autoType = autoType;
    }

    public ArrayList<PwEntryV4> getHistory() {
        return history;
    }

    public void setHistory(ArrayList<PwEntryV4> history) {
        this.history = history;
    }

    public void addToHistory(PwEntryV4 entry) {
	    history.add(entry);
    }

    public int sizeOfHistory() {
	    return history.size();
    }

    public String getAdditional() {
        return additional;
    }

    public void setAdditional(String additional) {
        this.additional = additional;
    }

    public String getTags() {
        return tags;
    }

    public void setTags(String tags) {
        this.tags = tags;
    }

    public void putCustomData(String key, String value) {
        customData.put(key, value);
    }

    public boolean containsCustomData() {
        return customData.size() > 0;
    }

    private static final long FIXED_LENGTH_SIZE = 128; // Approximate fixed length size
	public long getSize() {
		long size = FIXED_LENGTH_SIZE;

		for (Entry<String, ProtectedString> pair : fields.getListOfAllFields().entrySet()) {
			size += pair.getKey().length();
			size += pair.getValue().length();
		}
		
		for (Entry<String, ProtectedBinary> pair : binaries.entrySet()) {
			size += pair.getKey().length();
			size += pair.getValue().length();
		}
		
		size += autoType.defaultSequence.length();
		for (Entry<String, String> pair : autoType.entrySet()) {
			size += pair.getKey().length();
			size += pair.getValue().length();
		}
		
		for (PwEntryV4 entry : history) {
			size += entry.getSize();
		}
		
		size += overrideURL.length();
		size += tags.length();
		
		return size;
	}

	@Override
	public void createBackup(PwDatabase db) {
	    super.createBackup(db);

        PwEntryV4 copy = clone();
        copy.history = new ArrayList<>();
        history.add(copy);

        if (db != null)
	        if (db instanceof PwDatabaseV4)
                maintainBackups((PwDatabaseV4) db);
	}

	private boolean maintainBackups(PwDatabaseV4 db) {
		boolean deleted = false;

		int maxItems = db.getHistoryMaxItems();
		if (maxItems >= 0) {
			while (history.size() > maxItems) {
				removeOldestBackup();
				deleted = true;
			}
		}

		long maxSize = db.getHistoryMaxSize();
		if (maxSize >= 0) {
			while(true) {
				long histSize = 0;
				for (PwEntryV4 entry : history) {
					histSize += entry.getSize();
				}

				if (histSize > maxSize) {
					removeOldestBackup();
					deleted = true;
				} else {
					break;
				}
			}
		}

		return deleted;
	}

	private void removeOldestBackup() {
		Date min = null;
		int index = -1;

		for (int i = 0; i < history.size(); i++) {
			PwEntry entry = history.get(i);
			Date lastMod = entry.getLastModificationTime().getDate();
			if ((min == null) || lastMod.before(min)) {
				index = i;
				min = lastMod;
			}
		}

		if (index != -1) {
			history.remove(index);
		}
	}

	@Override
	public void touch(boolean modified, boolean touchParents) {
		super.touch(modified, touchParents);
		++usageCount;
	}

	@Override
	public void touchLocation() {
		parentGroupLastMod = new PwDate();
	}
	
	public boolean isSearchingEnabled() {
		if (parent != null) {
			return parent.isSearchingEnabled();
		}
		return PwGroupV4.DEFAULT_SEARCHING_ENABLED;
	}
}
