package com.gw.model.markattributeedit;

import android.util.Log;

import com.gw.ctrl.utils.AlgoUtil;
import com.gw.ctrl.utils.CrashUtil;

import java.util.ArrayList;
import java.util.List;

/**
 * Custom class used in AttributeEditForm class to capture, validate and format all the attribute tuple(s) when editing the Mark for an item
 */
public class MarkAttributeEntries{
    private final String TAG = "MarkAttributeEntries";   //Class tag for Logs

    private List<MarkAttributeTuple> allAttributeEntries;
    private String stringRepresentationOfAllEntries;
//    private final String KEYVAL_ENTRIES_SEPARATOR = ":";                      //Separates key and value entries within the tuple
    private final String KEYVAL_ENTRIES_SEPARATOR_W_QUOTES = "\":\"";           //Separates key and value entries within the tuple
//    private final String ATTRIBUTE_TUPLE_SEPARATOR = ",";                     //Separates each attribute tuple
    private final String ATTRIBUTE_TUPLE_SEPARATOR_W_QUOTES = "\", \"";         //Separates each attribute tuple
    private boolean haveValidTuples;                                //Valid attribute tuples are available if true
    private int countOfValidTuples;                                 //Count of all valid attribute tuples available, essentially the 'amount of attributes entered' in plain english

    public MarkAttributeEntries() {
        Log.i(TAG, "MarkAttributeEntries()");
        allAttributeEntries = new ArrayList<>();
        ensureThereIsAtLeastOneEntrySpaceAvailable();               //Basically calls addNewEmptyAttributeTuple() to set up first empty tuple
    }

    /**
     * Parse turns a string of JSON text into a Java object of MarkAttributeEntries.
     * Will be received from server as "\"Author\": \"Haruki\",\"Name\": \"Wind-Up Bird Chronicle\",\"No. Of Pages\": \"279\""
     * @param stringRepresentationToParseIn     String which contains the MarkAttributeEntries data to be parsed
     * @return
     */
    public boolean parseStringIn(String stringRepresentationToParseIn) {
        Log.i(TAG, "Parsing String below...\n" + stringRepresentationToParseIn);
        String dummyStringOne = "\"Author\":\"Haruki\", \"Name\": \"Wind-Up Bird Chronicle\",\"No. Of Pages\": \"279\"";

        String trimTest = " No. Of Pages ";
        Log.i(TAG, "testing trim |" + trimTest.trim() + "|");

        //Separate each tuple to a separate String via delimiter
        List<String> listOfSeparatedEntryStrings = AlgoUtil.separatedStringListViaSeparator(dummyStringOne, ATTRIBUTE_TUPLE_SEPARATOR_W_QUOTES);

        if(listOfSeparatedEntryStrings!=null) {
            //Second separation, with key and value stored on separate strings
            for(int i=0; i<listOfSeparatedEntryStrings.size(); i++) {
                String [] entryKeyAndValue = AlgoUtil.separatedStringArrayViaSeparator(listOfSeparatedEntryStrings.get(i), KEYVAL_ENTRIES_SEPARATOR_W_QUOTES);
                String keyEntryText = entryKeyAndValue[0];
                String valEntryText = entryKeyAndValue[1];

                //Add any valid tuple i.e. where both key and value entries are valid
                if(isAValidAttributeKeyEntry(keyEntryText) && isAValidAttributeValEntry(valEntryText)) {
                    addNewAttributeTuple(keyEntryText, valEntryText);
                }
            }
        }

        //Last check on all tuples to confirm if any valid tuples were counted
        if(hasAnyValidTuples()) {
            Log.i(TAG, "Parsing successful for " + countOfValidTuples + " attribute tuples.");
            return true;
        } else {
            CrashUtil.logCustomUserEvent("Ξ Parse Unsuccessful", stringRepresentationToParseIn);
            Log.d(TAG, "Parsing Unsuccessful.");
            return false;
        }
    }

    /**
     * Stringify turns this MarkAttributeEntries Java object into JSON text and returns that JSON text in a string.
     * Will be sent to server as "Author": "Haruki","Name": "Wind-Up Bird Chronicle","No. Of Pages": "279"
     * e.g. Print string as "Author": "Haruki","Name": "Wind-Up Bird Chronicle","No. Of Pages": "279"
     * TODO: Return null if no tuples available
     * @return
     */
    public String stringify() {
        Log.i(TAG, "stringifying...");
        stringRepresentationOfAllEntries = "";

        if(hasAnyValidTuples()) {
            //Trim last char i.e. comma (Tuple-Entry Separator) in valid cases
            if (stringRepresentationOfAllEntries != null && stringRepresentationOfAllEntries.length() > 0 ) {

                String stringWithRemovedSuffix = AlgoUtil.sansSuffixOfString(stringRepresentationOfAllEntries, ATTRIBUTE_TUPLE_SEPARATOR_W_QUOTES);

                if(stringWithRemovedSuffix!=null) {     //Suffix was successfully removed
                    stringRepresentationOfAllEntries = AlgoUtil.quotedString(stringWithRemovedSuffix).toString();
                }
            }
        } else {
            stringRepresentationOfAllEntries = null;
//            Log.e(TAG, "Stringify Error |" + stringRepresentationOfAllEntries + "|");
//            CrashUtil.logCustomUserEvent("Ξ Stringify Unsuccessful", stringRepresentationOfAllEntries);
        }

        return stringRepresentationOfAllEntries;
    }

    /**
     * Counts each tuple if they are valid and returns true if there are any valid tuples available
     * Valid Tuple examples: "Author": "Haruki" or "Name": "Wind-Up Bird Chronicle"
     * Invalid Tuple examples:  "": "Haruki" or "Name": "Wi" or "No  ": "279" (i.e. trims whitespaces scenario)
     * TODO: Incorporate this function into a way of letting the user know which tuples are invalid via client-side validation e.g. return all index positions of invalid tuples
     */
    public boolean hasAnyValidTuples() {
        Log.i(TAG, "hasAnyValidTuples()");
        haveValidTuples = false;
        countOfValidTuples = 0;
        stringRepresentationOfAllEntries = "";

        for(MarkAttributeTuple eachMarkAttributeTupleEntry:allAttributeEntries) {
            String eachAttributeKeyText = String.valueOf(eachMarkAttributeTupleEntry.getAttributeKey());
            String eachAttributeValText = String.valueOf(eachMarkAttributeTupleEntry.getAttributeValue());

            Log.i(TAG, "hasAnyValidTuples() ---- Checking KEY: " + eachAttributeKeyText + " || VALUE: " + eachAttributeValText);
            if( isAValidAttributeKeyEntry(eachAttributeKeyText) && isAValidAttributeValEntry(eachAttributeValText) ) {
                appendTupleToStringRepresentation(eachAttributeKeyText, eachAttributeValText);
                countOfValidTuples++;
                Log.i(TAG, "hasAnyValidTuples() ---- TUPLE VALID");
            } else {
                Log.w(TAG, "hasAnyValidTuples() ---- TUPLE INVALID");
            }
        }

        if(countOfValidTuples>0) {      //If there are are one or more valid attributes to log, return true
            Log.i(TAG, "hasAnyValidTuples() ---- " + countOfValidTuples);
            haveValidTuples = true;
        }

        return haveValidTuples;
    }

    /**
     * Append each tuple to the string representation, assuming validation was committed beforehand
     * @param keyToAppend
     * @param valueToAppend
     */
    public void appendTupleToStringRepresentation(String keyToAppend, String valueToAppend) {
        stringRepresentationOfAllEntries += keyToAppend + KEYVAL_ENTRIES_SEPARATOR_W_QUOTES + valueToAppend + ATTRIBUTE_TUPLE_SEPARATOR_W_QUOTES;
    }

    /**
     * Remove all tuples which are missing two valid key-value entries as expected
     */
    public void removeAllInvalidTuples() {
        Log.i(TAG, "removeAllInvalidTuples()");
        for(MarkAttributeTuple eachMarkAttributeTupleEntry:allAttributeEntries) {
            String eachAttributeKeyText = String.valueOf(eachMarkAttributeTupleEntry.getAttributeKey());
            String eachAttributeValText = String.valueOf(eachMarkAttributeTupleEntry.getAttributeValue());

            if( !isAValidAttributeKeyEntry(eachAttributeKeyText) || !isAValidAttributeValEntry(eachAttributeValText) ) {
                Log.i(TAG, "removeAllInvalidTuples() ---- Removing KEY: " + eachAttributeKeyText + " || VALUE: " + eachAttributeValText);
                //Remove present entry/attribute/tuple
                allAttributeEntries.remove(eachMarkAttributeTupleEntry);
            }
        }
    }

    public int getCountOfValidTuples() {
        return countOfValidTuples;
    }

    public void setCountOfValidTuples(int countOfValidTuples) {
        this.countOfValidTuples = countOfValidTuples;
    }

    /**
     * Checks if there is valid data in the string for an Attribute Key - validation function
     * @param keyStringToCheckIfValid
     * @return
     */
    private boolean isAValidAttributeKeyEntry(String keyStringToCheckIfValid) {
        if (!AlgoUtil.isAnEmptyString(keyStringToCheckIfValid) || AlgoUtil.hasMoreThanGivenAmountOfChars(keyStringToCheckIfValid, 3)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Checks if there is valid data in the string for an Attribute Value - validation function
     * @param valueStringToCheckIfValid
     * @return
     */
    private boolean isAValidAttributeValEntry(String valueStringToCheckIfValid) {
        if (!AlgoUtil.isAnEmptyString(valueStringToCheckIfValid) || AlgoUtil.hasMoreThanTwoChars(valueStringToCheckIfValid)) {
            return true;
        } else {
            return false;
        }
    }

    public void addNewAttributeTuple(String attributeKey, String attributeValue) {
        Log.i(TAG, "addNewAttributeTuple()");
        MarkAttributeTuple markAttributeTuple = new MarkAttributeTuple(attributeKey, attributeValue);
        addTupleToTheList(markAttributeTuple);
    }

    public void addNewEmptyAttributeTuple() {
        Log.i(TAG, "addNewEmptyAttributeTuple()");
        MarkAttributeTuple markAttributeTuple = new MarkAttributeTuple("", "");
        addTupleToTheList(markAttributeTuple);
    }

    public void insertAttributeKeyAtIndex(String attributeKeyText, int indexOfAttributeEntry) {
        ensureThereIsAtLeastOneEntrySpaceAvailable();
        Log.d(TAG, "insertAttributeKeyAtIndex() - attributeKeyText: " + attributeKeyText + "@indexOfAttributeEntry: " + indexOfAttributeEntry + " | Current Entry Size: " + allAttributeEntries.size());
        if(hasValidSpaceInListForIndex(indexOfAttributeEntry)) {
            MarkAttributeTuple currentMarkAttributeTuple = allAttributeEntries.get(indexOfAttributeEntry);
            currentMarkAttributeTuple.setAttributeKey(attributeKeyText);
        }
    }

    public void insertAttributeValueAtIndex(String attributeValueText, int indexOfAttributeEntry) {
        Log.d(TAG, "insertAttributeValueAtIndex() - Inserting attributeValueText: " + attributeValueText + "@indexOfAttributeEntry: " + indexOfAttributeEntry + " | Current Entry Size: " + allAttributeEntries.size());
        ensureThereIsAtLeastOneEntrySpaceAvailable();
        if(hasValidSpaceInListForIndex(indexOfAttributeEntry)) {
            MarkAttributeTuple currentMarkAttributeTuple = allAttributeEntries.get(indexOfAttributeEntry);
            currentMarkAttributeTuple.setAttributeValue(attributeValueText);
        }
    }

    /**
     * In case all attributes have been removed during validation or editing
     * We need to ensure there is space for more attributes.
     */
    public void ensureThereIsAtLeastOneEntrySpaceAvailable() {
        Log.i(TAG, "ensureThereIsAtLeastOneEntrySpaceAvailable()");
        if(allAttributeEntries.size()==0) {
            addNewEmptyAttributeTuple();
        }
    }

    public void addTupleToTheListAtIndex(MarkAttributeTuple markAttributeTupleToAdd, int indexOfAttribute) {
        Log.i(TAG, "addTupleToTheListAtIndex()");
        allAttributeEntries.add(indexOfAttribute, markAttributeTupleToAdd);
    }

    public void addTupleToTheList(MarkAttributeTuple markAttributeTupleToAdd) {
        Log.w(TAG, "addTupleToTheList() - Adding Tuple... " + markAttributeTupleToAdd.toString());
        allAttributeEntries.add(markAttributeTupleToAdd);
    }

    public void deleteTupleEntryAtIndex(int tupleIndexToRemove) {
        Log.d(TAG, "deleteTupleEntryAtIndex()");
        if(hasValidSpaceInListForIndex(tupleIndexToRemove)) {
            allAttributeEntries.remove(tupleIndexToRemove);
        }
    }

    /**
     * A check to avoid asking for an index which doesn't exist
     * @param indexToCheck
     * @return
     */
    public boolean hasValidSpaceInListForIndex(int indexToCheck) {
        if(allAttributeEntries.size()>indexToCheck) { //There are more available slots than the asked Index for check
            Log.i(TAG, "hasValidSpaceInListForIndex() - TRUE - indexToCheck: " + indexToCheck);
            return true;
        } else {
            Log.e(TAG, "hasValidSpaceInListForIndex() - FALSE - indexToCheck: " + indexToCheck);
            return false;
        }
    }

    /**
     * getter for string representation of this class
     * NOTE; This class should go through the 'stringify' function before calling this method, otherwise it will return null
     * @return
     */
    public String getStringRepresentationOfAllEntries() {
        return stringRepresentationOfAllEntries;
    }

    public void setStringRepresentationOfAllEntries(String stringRepresentationOfAllEntries) {
        this.stringRepresentationOfAllEntries = stringRepresentationOfAllEntries;
    }

    @Override
    public String toString() {
        Log.i(TAG, "toString()");
        return "MarkAttributeEntries{" +
                "allAttributeEntries=" + allAttributeEntries +
                '}';
    }
}

/**
 * Class which captures the key and value embodying each Attribute entry e.g. X="Author" and Y="Haruki"
 * @param <X>
 * @param <Y>
 */
class MarkAttributeTuple<X, Y> {
    public X attributeKey;
    public Y attributeValue;

    public MarkAttributeTuple(X attributeKey, Y attributeValue) {
        this.attributeKey = attributeKey;
        this.attributeValue = attributeValue;
    }

    public void setAttributeKey(X attributeKey) {
        this.attributeKey = attributeKey;
    }

    public void setAttributeValue(Y attributeValue) {
        this.attributeValue = attributeValue;
    }

    public X getAttributeKey() {
        return attributeKey;
    }

    public Y getAttributeValue() {
        return attributeValue;
    }

    @Override
    public String toString() {
        return "\n MarkAttributeTuple{" +
                "X=" + attributeKey +
                ", Y=" + attributeValue +
                '}' + "\n";
    }
}