package com.bernard.mcqinator.api.quiz;

import android.app.ProgressDialog;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

import com.bernard.mcqinator.api.IdentifiedQuiz;
import com.bernard.mcqinator.api.frageable.BlocFrageable;
import com.bernard.mcqinator.api.fragen.BlocFrage;
import com.bernard.mcqinator.api.fragen.Frage;
import com.bernard.mcqinator.api.fragenStats.FormQuizStat;
import com.bernard.mcqinator.inutil.ArrayInutil;
import com.bernard.mcqinator.inutil.NumberInutil;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import static com.bernard.mcqinator.inutil.ArrayInutil.asize;
import static com.bernard.mcqinator.inutil.ArrayInutil.count;
import static com.bernard.mcqinator.inutil.ArrayInutil.range;
import static com.bernard.mcqinator.inutil.ArrayInutil.toObjectArray;

/**
 * @author Mysaa
 * @date on 28/05/17.
 */

public abstract class FormQuiz extends Quiz implements BlocFrageable<FormQuizStat,FormQuiz.Identifier> {

    private FormQuiz.Item[] items;
    private String[] idS;

    //TODO make forms no-declareable (withs forms IDs)

    //////////// BlocFrage ////////////
    @Override
    public IdentifiedQuiz<BlocFrage,Identifier> genFragen(FormQuizStat stat, BlocData frageData, Context context, ProgressDialog wait) {

        // Initialisation des variables
        Random r = new Random();

        // Correction des potentielles erreurs du quiz
        if(items.length <= 0)
            throw new IllegalStateException("There is no frage in this quiz !");//TODO implement special error, that is toasted to the user

        // Selection des items
        List<Integer> itemsPos = asize(range(0,items.length),frageData.length,toObjectArray(stat.failSums()),stat.failSum(),r);
        Map<Integer,Integer> itemsPosAndCount = count(itemsPos);

        //Selection de la form à trouver

        Map<Integer[],Integer> formsPosAndCount = new HashMap<>();

        for(Map.Entry<Integer,Integer> e : itemsPosAndCount.entrySet()){
            Integer[][] formsIDs = new Integer[items[e.getKey()].forms.length][2];
            for (int i = 0; i < formsIDs.length; i++)
                formsIDs[i] = new Integer[]{e.getKey(),i};
            Map<Integer[],Integer> formPosAndCount = count(asize(formsIDs,e.getValue(),toObjectArray(stat.failSums(e.getKey())),stat.failSum(e.getKey()),r));
            formsPosAndCount.putAll(formPosAndCount);
        }

        // Selection de la form question
        Map<Integer[],Integer> fformsPosAndCount = new HashMap<>();

        for(Map.Entry<Integer[],Integer> e : formsPosAndCount.entrySet()){
            int itemID = e.getKey()[0];
            int formID = e.getKey()[1];
            List<Integer[]> fformsIDs = new ArrayList<>();
            for (int i = 0; i < items[itemID].forms.length - 1; i++)
                fformsIDs.add(new Integer[]{itemID,formID, i});
            Log.v("R","Rutabaga" + itemID);
            Map<Integer[],Integer> fformPosAndCount = count(asize(fformsIDs,e.getValue(),toObjectArray(stat.failSums(itemID,formID)),stat.failSum(itemID,formID),r));
            Log.v("R","Salsifi");
            fformsPosAndCount.putAll(fformPosAndCount);
        }

        // Selection des mauvaises réponces
        List<BlocFrage> fragen = new ArrayList<>();


        for(Map.Entry<Integer[],Integer> e : fformsPosAndCount.entrySet()){
            int itemID = e.getKey()[0];
            int formID = e.getKey()[1];
            int fformID = e.getKey()[2];
            String frageText = getBlocFrageText(itemID,formID, NumberInutil.exclude(fformID,formID),frageData,context);
            Item.Form form = items[itemID].getForms()[formID];
            String richtigAntwort = form.getRichtig();
            List<Integer[]> falsheIDs = new ArrayList<>();
            for (int i = 0; i < items[itemID].forms[formID].getFalshe().length; i++)
                falsheIDs.add(new Integer[]{itemID,formID,fformID,i});
            List<Integer[]> falshPoz = asize(falsheIDs,e.getValue() * (frageData.blockNumber - 1),toObjectArray(stat.failSums(itemID,formID,fformID)),stat.failSum(itemID,formID,fformID),r);
            for (int i = 0; i < falshPoz.size(); i+=(frageData.blockNumber - 1)) {
                List<Integer[]> falsheBloc = falshPoz.subList(i,i+(frageData.blockNumber - 1));
                String[] falsheBlocArray = new String[falsheBloc.size()];
                for (int j = 0; j < falsheBlocArray.length; j++)
                    falsheBlocArray[j] = form.getFalshe()[falsheBloc.get(j)[3]];

                fragen.add(new BlocFrage(frageText,richtigAntwort,falsheBlocArray));
            }

        }

        List<Integer[]> idsFragen = new ArrayList<>(fformsPosAndCount.keySet());
        List<Integer> poss = Arrays.asList(ArrayInutil.range(0,idsFragen.size()));
        Collections.shuffle(poss,r);

        Integer[][] shuffledIdsFragen = new Integer[idsFragen.size()][];
        BlocFrage[] shuffledFragenArray = new BlocFrage[fragen.size()];

        for (int i = 0; i < poss.size(); i++) {
            shuffledIdsFragen[i] = idsFragen.get(poss.get(i));
            shuffledFragenArray[i] = fragen.get(poss.get(i));
        }
        Log.v("BlocFrageGenerator","Done !");

        return new IdentifiedQuiz<>(shuffledFragenArray,new Identifier(ArrayInutil.toPrimitiveArray(shuffledIdsFragen)));
    }

    public abstract String getBlocFrageText(int itemID, int formID, int fformID, BlocData frageData, Context context);

    @Override
    public FormQuizStat updateStats(FormQuizStat oldStats, List<BlocFrage.Reply> replies,FormQuiz.Identifier identifier) {
        if(replies.size() != identifier.formIndexes.length)throw new IllegalArgumentException("The identifier dont match with the number of replies recieved");
        for (int i = 0; i < replies.size(); i++) {
            int itemID = identifier.formIndexes[i][0];
            int formID = identifier.formIndexes[i][1];
            int fformID = identifier.formIndexes[i][2];
            String answered = replies.get(i).getAnswered();
            if(items[itemID].getForms()[formID].getRichtig().equals(answered))
                continue;
            int falsheAntwortID = ArrayInutil.indexOf(items[itemID].getForms()[formID].getFalshe(),answered);
            oldStats.addAFail(itemID,formID,fformID,falsheAntwortID);
        }
        return oldStats;
    }

    @Override
    public void genDefault() {
        items = new FormQuiz.Item[0];
    }


    //////////// Getters / Setters ////////////

    //TODO set setters for editactivities

    public void addItem(Item i){
        Item[] litem = new Item[items.length+1];
        System.arraycopy(items,0,litem,0,items.length);
        litem[litem.length-1] = i;
        items = litem;
    }

    public void setItem(int pos,Item i){
        items[pos] = i;
    }

    public FormQuiz.Item[] getItems() {
        return items;
    }

    public FormQuiz.Item getItem(int pos) {
        return items[pos];
    }


    //////////// Customization methods ////////////

    //TODO add more abstract methods for a full customisation

    public abstract int getEditActivityLayoutID();

    public abstract int getEditItemActivityFormLayoutID();

    public abstract int getEditItemActivityFalseLayoutID();

    public abstract int getEditItemActivityLayoutID();

    protected abstract View getEditQuizItemView(Item item, int position, View convertView, ViewGroup parent, Context c);

    public abstract int getFormNameStringId(int pos);

    public abstract int getFormCount();


    //////////// File Methods ////////////

    public void write(DataOutputStream dos) throws IOException{
        dos.writeInt(items.length);
        for (Item i : items)
            i.write(dos);
    }

    public void read(DataInputStream dis) throws IOException{
        items = new Item[dis.readInt()];
        for (int i = 0; i < items.length; i++) {
            items[i] = new Item();
            items[i].read(dis);
        }
    }


    //////////// Internal Classes ////////////

    //TODO implement all
    //TODO remove parcelable and serialiable if not used
    public static class Item implements Parcelable,Serializable {

        Form[] forms;

        public Item(Form[] forms) {
            this.forms = forms;
        }

        public Item() {} // For serialization

        //////////// Getters / Setters ////////////

        public Form[] getForms() {
            return forms;
        }

        public void setForms(Form[] forms) {
            this.forms = forms;
        }

        //////////// Parcelable ////////////

        protected Item(Parcel in) {
            forms = in.createTypedArray(Form.CREATOR);
        }

        public static final Creator<Item> CREATOR = new Creator<Item>() {
            @Override
            public Item createFromParcel(Parcel in) {
                return new Item(in);
            }

            @Override
            public Item[] newArray(int size) {
                return new Item[size];
            }
        };

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeTypedArray(forms,0);
        }

        //////////// File Methods ////////////

        public void write(DataOutputStream dos) throws IOException{
            dos.writeInt(forms.length);
            for (Form f : forms)
                f.write(dos);
        }

        public void read(DataInputStream dis) throws IOException{
            forms = new Form[dis.readInt()];
            for (int i = 0; i < forms.length; i++) {
                forms[i] = new Form();
                forms[i].read(dis);
            }
        }

        //////////// Internal Classes ////////////

        public static class Form implements Parcelable,Serializable {

            String richtig;
            String[] falshe;
            int formID;

            public Form(int formID,String richtig, String... falshe) {
                this.richtig = richtig;
                this.falshe = falshe;
                this.formID = formID;
            }

            protected Form(Parcel in) {
                formID = in.readInt();
                richtig = in.readString();
                falshe = in.createStringArray();
            }

            public Form() {} //For serialization

            //////////// Getters / Setters ////////////

            public String getRichtig() {
                return richtig;
            }

            public String[] getFalshe() {
                return falshe;
            }

            public int getFormID() {
                return formID;
            }

            /////////// File Methods ////////////

            public void write(DataOutputStream dos) throws IOException{
                dos.writeInt(formID);
                if(richtig == null) {
                    dos.writeInt(0);
                }else{
                    dos.writeInt(richtig.getBytes().length);
                    dos.write(richtig.getBytes());
                }

                dos.writeInt(falshe.length);
                for(String f : falshe){
                    if(f == null){
                        dos.writeInt(0);
                    }else {
                        dos.writeInt(f.getBytes().length);
                        dos.write(f.getBytes());
                    }
                }
            }

            public void read(DataInputStream dis) throws IOException{
                formID = dis.readInt();
                byte[] richtigBytes = new byte[dis.readInt()];
                int read = dis.read(richtigBytes);
                if(read != richtigBytes.length)throw new IOException("Fichier invalide ... la taille annoncée du string richtig n'est pas satisfaite ");
                richtig = new String(richtigBytes);
                falshe = new String[dis.readInt()];
                for (int i = 0; i < falshe.length; i++) {
                    byte[] falshBytes = new byte[dis.readInt()];
                    read = dis.read(falshBytes);
                    if(read != falshBytes.length)throw new IOException("Fichier invalide ... la taille annoncée du string falche n°"+i+" n'est pas satisfaite ");
                    falshe[i] = new String(falshBytes);
                }
            }

            //////////// Parcelable ////////////

            public static final Creator<Form> CREATOR = new Creator<Form>() {
                @Override
                public Form createFromParcel(Parcel in) {
                    return new Form(in);
                }

                @Override
                public Form[] newArray(int size) {
                    return new Form[size];
                }
            };

            @Override
            public int describeContents() {
                return 0;
            }

            @Override
            public void writeToParcel(Parcel dest, int flags) {
                dest.writeInt(formID);
                dest.writeString(richtig);
                dest.writeStringArray(falshe);
            }

            /////////// Misc ////////////

            @Override
            public String toString() {
                return "Form{" +
                        "richtig='" + richtig + '\'' +
                        ", falshe=" + Arrays.toString(falshe) +
                        ", formID=" + formID +
                        '}';
            }
        }

        //////////// Misc ////////////

        @Override
        public String toString() {
            return "Item{" +
                    "forms=" + Arrays.toString(forms) +
                    '}';
        }

    }

    public static class Identifier extends Frage.Identifier{
        /*
        Array of type {[ItemPos,FormPos,FformPos],[ItemPos,FormPos,FformPos],...}
         */
        final int[][] formIndexes;


        public Identifier(int[][] formIndexes) {
            this.formIndexes = formIndexes;
        }
    }

    //LATER move
    public class ItemAdapter extends BaseAdapter{

        final Context c;

        public ItemAdapter(Context c) {
            this.c = c;
        }

        @Override
        public int getCount() {
            return items.length;
        }

        @Override
        public Item getItem(int position) {
            return items[position];
        }

        @Override
        public long getItemId(int position) {
            return 0;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            return FormQuiz.this.getEditQuizItemView(items[position], position, convertView, parent,c);
        }
    }


    //////////// Misc ////////////

    @Override
    public String toString() {
        return "DEVerbenQuiz (" + Arrays.deepToString(items) + ")";
    }
}
