package com.bernard.mcqinator.controller;

import android.content.Context;
import android.support.annotation.Nullable;
import android.util.Log;

import com.bernard.mcqinator.api.QuizMetadata;
import com.bernard.mcqinator.api.fragenStats.QuizStat;
import com.bernard.mcqinator.api.quiz.Quiz;
import com.bernard.mcqinator.inutil.NumberInutil;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;

/**
 * @author Mysaa
 * @date on 22/07/17.
 */

public final class FileManager {

    private static final Random fileNamesRandom = new Random();


    //////////// Consts ////////////

    public static final byte[] appBernardKey = {0b00100010,0b01001001};
    public static final byte[] appBernardKeyStat = {0b00100011,0b01001000};
    public static final byte[] bernardSignature = {0b00011001,0b01101101};


    //////////// QuizStat ////////////

    public static String statFilename(long fileID){
        return "stat_"+ NumberInutil.toUnsignedString(fileID)+".bernard";
    }

    public static String statFilename(QuizMetadata quiz){
        return statFilename(quiz.getFileID());
    }

    ////// Save //////

    public static void saveStat(QuizMetadata quiz,QuizStat stat,Context ctx){
        try {
            ctx.deleteFile(statFilename(quiz));
            saveStatAsFile(stat,quiz,new DataOutputStream(ctx.openFileOutput(statFilename(quiz),Context.MODE_PRIVATE)));
        } catch (IOException e) {
            Log.e("FileManager","Can't save the quiz stat " + stat,e);
        }
    }

    public static void saveStatAsFile(QuizStat stat,QuizMetadata meta,DataOutputStream dos,boolean closeDatautputStream) throws IOException {
        //LATER implement bernard API
        //Writing Bernard signature
        dos.write(FileManager.bernardSignature);

        //Writing Bernard key
        dos.write(FileManager.appBernardKeyStat);

        //Writing fileType
        dos.writeInt(meta.getType().getId());

        //Writing quiz
        stat.write(dos);

        if(closeDatautputStream)dos.close();
    }

    public static void saveStatAsFile(QuizStat stat,QuizMetadata meta,DataOutputStream dos) throws IOException {
        saveStatAsFile(stat,meta,dos,true);
    }

    ////// Get //////

    public static QuizStat getStat(QuizMetadata quiz, Context ctx){
        try {
            return getFromFile(new DataInputStream(ctx.openFileInput(statFilename(quiz.getFileID()))));
        }catch (IOException e) {
            Log.e("FileManager","Cannot read QuizStat of quiz " + quiz,e);
        }
        return null;
    }

    public static QuizStat getFromFile(DataInputStream din) throws IOException {

        //LATER implement bernard API
        int readBytes;

        //Assert bernard signature
        byte[] bernardSignature = new byte[2];
        readBytes = din.read(bernardSignature);
        if(readBytes != 2 || bernardSignature[0] != FileManager.bernardSignature[0] || bernardSignature[1] != FileManager.bernardSignature[1]){
            din.close();
            throw new IOException("Corrupted quizStat : wrong bernard signature");
        }


        byte[] bernardType = new byte[2];
        readBytes = din.read(bernardType);
        if(readBytes != 2 || bernardType[0] != FileManager.appBernardKeyStat[0] || bernardType[1] != FileManager.appBernardKeyStat[1]) {
            din.close();
            throw new IOException("Corrupted quizStat : wrong QuizStat key");
        }

        int quizTypeNum = din.readInt();
        QuizMetadata.Type type = QuizMetadata.Type.getTypeFromID(quizTypeNum);
        if(type == null){
            din.close();
            throw new IOException("Corrupted quizStat : unknown QuizType ID");
        }

        QuizStat actual;
        try{
            actual = type.getQuizStatClass().newInstance();
            actual.read(din);
            Log.v("FileManager","QuizStat successfully read !");
        }catch(Exception e) {
            din.close();
            throw new IOException("Corrupted quizStat : unreadable content", e);
        }
        din.close();
        return actual;
    }


    //////////// Quiz ////////////

    private static String quizFilename(long fileID){
        return "quiz_"+ NumberInutil.toUnsignedString(fileID)+".bernard";
    }

    private static String quizFilename(QuizMetadata quiz){
        return quizFilename(quiz.getFileID());
    }

    ////// Save //////

    public static void saveQuiz(QuizMetadata meta,Quiz q,Context ctx){
        try {
            ctx.deleteFile(quizFilename(meta));
            saveQuizAsFile(new DataOutputStream(ctx.openFileOutput(quizFilename(meta),Context.MODE_PRIVATE)), meta, q);
        } catch (IOException e) {
            Log.e("FileManager","Cant save the quiz " + q + "at" + meta,e);
        }
    }

    private static void saveQuizAsFile(DataOutputStream dos,QuizMetadata meta, Quiz q,boolean justPayload,boolean closeDataOutputStream) throws IOException {

        if(!justPayload) {
            //Writing Bernard signature
            dos.write(FileManager.bernardSignature);

            //Writing Bernard key
            dos.write(FileManager.appBernardKey);
        }

        //Writing quizType key
        dos.writeInt(meta.getType().getId());

        //Writing quiz
        q.write(dos);

        if(closeDataOutputStream)dos.close();
    }

    private static void saveQuizAsFile(DataOutputStream dos,QuizMetadata meta, Quiz q) throws IOException {
        saveQuizAsFile(dos,meta,q,false,true);
    }

    ////// Get //////

    @Nullable
    private static Quiz getQuizFromFile(DataInputStream dis,boolean justPayload) throws IOException {

        int readBytes;

        if(!justPayload) {

            //Assert bernard signature
            byte[] bernardSignature = new byte[2];
            readBytes = dis.read(bernardSignature);
            if(readBytes != 2 || bernardSignature[0] != FileManager.bernardSignature[0] || bernardSignature[1] != FileManager.bernardSignature[1]){
                dis.close();
                throw new IOException("Corrupted quiz : wrong bernard signature");
            }


            byte[] bernardType = new byte[2];
            readBytes = dis.read(bernardType);
            if(readBytes != 2 || bernardType[0] != FileManager.appBernardKey[0] || bernardType[1] != FileManager.appBernardKey[1]) {
                dis.close();
                throw new IOException("Corrupted quiz : wrong Quiz key");
            }
        }
        int quizTypeNum = dis.readInt();
        QuizMetadata.Type type = QuizMetadata.Type.getTypeFromID(quizTypeNum);
        if(type == null){
            dis.close();
            throw new IOException("Unknown quiz type");
        }

        Quiz actual;
        try{
            actual = type.getQuizClass().newInstance();
            actual.read(dis);
            Log.v("FileManager","Quiz successfully read");
        }catch(Exception e){
            dis.close();
            throw new IOException("Corrupted quiz : wrong content",e);
        }
        dis.close();
        return actual;
    }

    private static Quiz getQuizFromFile(DataInputStream dis) throws IOException {
        return getQuizFromFile(dis,false);
    }

    public static Quiz getQuiz(QuizMetadata meta,Context ctx) throws IOException {
        try {
            return getQuizFromFile(new DataInputStream(ctx.openFileInput(quizFilename(meta.getFileID()))));
        }catch (IOException e) {
            Log.e("FileManager","Cannot read Quiz with meta " + meta,e);
        }
        return null;
    }

    ////// Import / Export //////

    public static void exportQuizToFile(QuizMetadata meta,Context ctx,File f){
        try {
            //LATER implement Bernard API
            DataOutputStream dos = new DataOutputStream(new FileOutputStream(f));
            long temp = meta.getFileID();
            meta.setFileID(-1);
            meta.write(dos);
            meta.setFileID(temp);
            saveQuizAsFile(dos,meta,getQuiz(meta,ctx),true,true);//Do the dos.close()
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Nullable
    public static QuizMetadata importQuizFromFile(Context ctx,File f) throws IOException {
        DataInputStream dis = new DataInputStream(new FileInputStream(f));

        int readBytes;

        byte[] bernardSignature = new byte[2];
        readBytes = dis.read(bernardSignature);
        if(readBytes != 2 || bernardSignature[0] != FileManager.bernardSignature[0] || bernardSignature[1] != FileManager.bernardSignature[1]){
            dis.close();
            throw new IOException("Corrupted quiz : wrong bernard sign");
        }

        byte[] bernardType = new byte[2];
        readBytes = dis.read(bernardType);
        if(readBytes != 2 || bernardType[0] != FileManager.appBernardKey[0] || bernardType[1] != FileManager.appBernardKey[1]) {
            dis.close();
            throw new IOException("Corrupted quiz : wrong app sign");
        }

        //TODO add special key to QUIZ_WITH_METADATA

        QuizMetadata toImport = new QuizMetadata();
        toImport.read(dis);

        Quiz q = getQuizFromFile(dis,true);

        QuizMetadata newMetadata = QuizMetadataManager.createQuiz(ctx,toImport.getName(),toImport.getAuthor(),toImport.getType(),toImport.isEditable());
        saveQuiz(newMetadata,q,ctx);
        return newMetadata;
    }


    //////////// Global ////////////

    public static long getAvalivableFileID(Context ctx){
        long fileID;
        do{
            fileID = fileNamesRandom.nextLong();
        }while(new File(ctx.getFilesDir(),quizFilename(fileID)).exists() || new File(ctx.getFilesDir(),statFilename(fileID)).exists());
        return fileID;
    }

    public static boolean delete(QuizMetadata m,Context c) {
        return c.deleteFile(quizFilename(m)) && !c.deleteFile(statFilename(m)) && QuizMetadataManager.delete(m);
    }





}
