package gitlet;

import java.io.File;
import static gitlet.Utils.*;

import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.*;
// TODO: any imports you need here

/** Represents a gitlet repository.
 *  TODO: It's a good idea to give a description here of what else this Class
 *  does at a high level.
 *
 *  @author Tianye Meng
 */
public class Repository {
    /**
     * TODO: add instance variables here.
     *This class represents the whole state of .gitlet repository. It contains 3
     * sub-directories, blobs, commits, and staging.
     * 1.blobs contains all the file that has been ever created in Gitlet.
     * The files that contains the same contents would not be created again.In each
     * commits, there will be some pointers pointing toward corresponding files in blobs.
     * 2. commits contains a number of Commit objects. It keeps track of all commits using
     * a tree structure. Specific commit implementation is in Commit class.
     * 3. Staging area. Staging area is a temporary directory to store files. It stores the copies
     * of some files and delete all the files inside after some commands being executed.
     *
     * List all instance variables of the Repository class here with a useful
     * comment above them describing what that variable represents and how that
     * variable is used. We've provided two examples for you.
     */

    /** The current working directory. */
    public static final File CWD = new File(System.getProperty("user.dir"));
    /** The .gitlet directory. */
    public static final File GITLET_DIR = join(CWD, ".gitlet");
    public static final File COMMIT_DIR = join(GITLET_DIR, "commits");
    public static final File STAGE_DIR = join(GITLET_DIR, "staging");
    public static final File BLOB_DIR = join(GITLET_DIR, "blobs");
    public static final File BRANCH_DIR = join(GITLET_DIR, "branch");
    public static final File STADD_DIR = join(STAGE_DIR, "add");
    public static final File STDEL_DIR = join(STAGE_DIR, "delete");
    public static final File head = join(GITLET_DIR, "head");
    //public LinkedList<Commit> DefBranch;



    public void init(){
        if(GITLET_DIR.exists()){
            System.out.println("A Gitlet version-control system already exists in the current directory.");
            System.exit(0);
        }else{
            GITLET_DIR.mkdir();//.gitlet directory
            BLOB_DIR.mkdir();//stores blobs
            COMMIT_DIR.mkdir();//stores all the commits
            STAGE_DIR.mkdir();//staging area
            BRANCH_DIR.mkdir();//stores all the branches
            STADD_DIR.mkdir();//stage for addition
            STDEL_DIR.mkdir();//stage for deletion

            Commit initial = new Commit();
            Utils.writeObject(head, initial);
            File toCom = join(COMMIT_DIR, initial.getUid());
            writeObject(toCom, initial);
//            DefBranch = new LinkedList<Commit>();
//            DefBranch.add(initial);
//            File main = join(BRANCH_DIR, "Main");
//            Utils.writeObject(main, DefBranch);

        }
    }

    /**
     * add a file that exists in CWD to staging area.
     * Main function: add a copy of an existing file to staging area. if this file already exists,
     * but have different contents, overwrite contents it with the new contents. If an identical
     * file already exists, do not change it and then remove it if it's staged for deletion.
     * @param name
     */
    public void gitAdd(String name){
        File toAdd = Utils.join(CWD, name);
        if(!toAdd.exists()){
            System.out.println("File does not exist.");
            System.exit(0);
        }
        File existed = join(STADD_DIR, name);
        if(existed.exists() &&
                Utils.sha1(Utils.serialize(existed)).equals(Utils.sha1(Utils.serialize(toAdd)))){
            restrictedDelete(join(STDEL_DIR, name));
            return;
        }
        Utils.writeContents(existed, serialize(toAdd));
        //in add command, do nothing with blobs

    }

    /*
    1. create a new Commit whose parent is the current head commit
    2. if there are some files in staging area that are not in commit,
           1. if they are in BLOB_DIR:
            If so, add the pointer to the commit's blob.
            If not, create a blob first, and then add it to the commit's blob.
     3. if no changes has been made, print no changes.
     4. add this new commit to the commit tree.
     */
    public void gitCommit(String msg){
        Commit par = Utils.readObject(head, Commit.class);//parent commit
        Commit cur = new Commit(msg, par.getBlobs(), par, null);//commit to be added
        List<String> lst = Utils.plainFilenamesIn(STADD_DIR);//lst of file name staged for addition
        boolean changed = false;//record if any changes have been made.

        for(String toCom : lst){
            changed = true;
            File f = join(STADD_DIR, toCom);//traverse every file in STADD

            if(!cur.contains(Utils.sha1(serialize(f)))){
                cur.setBlobs(f, toCom);

                File blob = join(BLOB_DIR, Utils.sha1(serialize(f)));
                if(!blob.exists()){
                    writeContents(blob, readContents(f));
                }
            }
            //Utils.restrictedDelete(f);//remove toCom from STADD
        }
        if(!changed){
            System.out.println("No changes added to the commit.");
        }
//        DefBranch.add(cur);//add new commit to commit tree
        Utils.writeObject(head, cur);//redirect head pointer
        File com = join(COMMIT_DIR, cur.getUid());//add current commit to COM_DIR
        Utils.writeObject(com, cur);//same as above
    }

    /*
    Remove this file from STADD if it exists. Then stage it for deletion.
    If it exists in CWD and it's tracked, remove it.
     */
//    public void gitRm(String name){
//        File staged = join(STADD_DIR, name);
//        if(staged.exists()){
//            File toDel = join(STDEL_DIR, name);
//            Utils.writeContents(toDel, readContents(staged));
//        }
//        File existed = join(CWD, name);
        //check if existed actually exists and has been tracked
        // in current commit and if so, delete it.
    //}

    public void gitCheckOutHead(String name){
        Commit cur = Utils.readObject(head, Commit.class);
        gitCheckOut(cur.getUid(), name);
    }
    /*
    locate a commit and then replace the file "name" in CWD with the same file (namely)
    in that Commit.
     */
    public void gitCheckOut(String uid, String name){

        if(! Utils.plainFilenamesIn(COMMIT_DIR).contains(uid)){
            System.out.println("No commit with that id exists.");
            System.exit(0);
        }
        Commit cur = Utils.readObject(join(COMMIT_DIR, uid), Commit.class);
        if(! cur.getBlobs().containsValue(name)){
            System.out.println("File does not exist in that commit.");
            System.exit(0);
        }
        String sha1 = "";
        /*
        get the sha1 value of the name file in commit
         */
        for(Entry<String, String> entry : cur.getBlobs().entrySet()){
            if(entry.getValue().equals(name)){
                sha1 = entry.getKey();
            }
        }

        File toReplace = join(BLOB_DIR, sha1);
        File beReplaced = join(CWD, name);
        byte[] content = readContents(toReplace);
        Utils.writeContents(beReplaced, content);
    }

    public void gitLog(){
        List<String> comLst = Utils.plainFilenamesIn(COMMIT_DIR);
        Commit headCom = readObject(head, Commit.class);
        while(true){
            System.out.println("===");
            System.out.println("commit "+headCom.getUid());
            System.out.println("Date: "+headCom.getTimeStamp());
            System.out.println(headCom.getMessage());
            System.out.println();
            if(headCom.getParent1() == null){
                break;
            }
            headCom = headCom.getParent1();

        }
    }

    /*
    check if the given args array have exactly num elements and
    if a .gitlet file has been created.
     */
    public void argCheck(String[] args, int num) {
        if (args.length != num) {
            System.out.println("Incorrect operands.");
            System.exit(0);
        } else if (!GITLET_DIR.exists()) {
            System.out.println("Not in an initialized Gitlet directory.");
            System.exit(0);
        }
    }

    /*
    Check if such a target file exists and returns
    true if target has the same sha1 value as shaone,
    false otherwise
     */
    public boolean contains(File target, String shaone){
        if(target.exists() &&
                Utils.sha1(Utils.serialize(target)).equals(Utils.sha1(shaone))){
            return true;
        }
        return false;
    }
    /* TODO: fill in the rest of this class. */
}
