/*
Copyright (C) 2005 David Green <green@couchpotato.net>
All Rights Reserved.

This file is part of Aelfengard.

Aelfengard is proprietary software. You may not redistribute it without
prior written permission from the copyright holder.
*/

package server.command;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import common.CommonUtils;

import server.AdminFlag;
import server.Player;
import server.SyntaxException;
import server.Utils;

public class IdeaCommand extends Command {

    private static final Set<Idea> ideasSorted = new TreeSet<Idea>();
    private static final Map<Integer,Idea> ideasById = new HashMap<Integer,Idea>();
    
    private static int nextId = 1;
    
    public IdeaCommand() {
        super(CommandCategory.SYSTEM, "Idea submission/voting system.");
    }
    
    @Override
    public void run(Player player) throws SyntaxException {
        String subcmd = CommandProcessor.nextToken();
        if (subcmd == null) {
            throw new SyntaxException();
        }
        if (subcmd.equals("list")) {
            if (CommandProcessor.nextToken() != null) {
                throw new SyntaxException();
            }
            boolean adminView = player.getAdminFlags().contains(AdminFlag.ADMIN_VIEWS);
            boolean gotOne = false;
            String[][] data = new String[ideasSorted.size()][4];
            String[] headers = new String[] { "id", "submitter", "votes", "description" };
            int[] sizes = new int[] { 40, 100, 40 }; 
            int idx = 0;
            for (Idea idea : ideasSorted) {
                gotOne = true;
                String submitter;
                if (adminView || idea.player == player.getId()) {
                    submitter = Utils.capitalize(player.getNickname(idea.player, true));
                }
                else {
                    submitter = "";
                }
                data[idx][0] = String.valueOf(idea.ideaId);
                data[idx][1] = submitter;
                data[idx][2] = String.valueOf(idea.votes.size());
                data[idx][3] = idea.description;
                idx++;
            }
            player.sendTable(data, headers, sizes);
            if (!gotOne) {
                player.sendText(true, "No ideas found. Why don't you submit one?");
                return;
            }
        }
        else if (subcmd.equals("submit")) {
            String description = CommandProcessor.getRemaining();
            if (description == null || description.length() == 0) {
                throw new SyntaxException();
            }
            //description = Utils.prettyMessage(description);
            Idea idea = new Idea(player.getId(), description);
            ideasById.put(idea.ideaId, idea);
            ideasSorted.add(idea);
            player.sendText(true, "Your idea has been submitted, and has been assigned #" + idea.ideaId);
        }
        else if (subcmd.equals("delete")) {
            String sId = CommandProcessor.nextToken();
            if (sId == null || CommandProcessor.nextToken() != null) {
                throw new SyntaxException();
            }
            if (sId.startsWith("#")) {
                sId = sId.substring(1);
            }
            int ideaId;
            try {
                ideaId = Integer.parseInt(sId);
            }
            catch (NumberFormatException ex) {
                throw new SyntaxException();
            }
            Idea idea = ideasById.get(ideaId);
            if (idea == null) {
                player.sendText(true, "Invalid idea number: " + ideaId);
                return;
            }
            if (!player.getAdminFlags().contains(AdminFlag.ADMIN_BUILDER)) {
                if (idea.player != player.getId() || idea.votes.size() > 0) {
                    player.sendText(true, "You can only delete ideas that were submitted by you and have no votes.");
                    return;
                }
            }
            ideasSorted.remove(idea);
            ideasById.remove(idea.ideaId);
            player.sendText(true, "Idea #" + idea.ideaId + " has been deleted.");
        }
        else if (subcmd.equals("vote")) {
            String sId = CommandProcessor.nextToken();
            if (sId == null || CommandProcessor.nextToken() != null) {
                throw new SyntaxException();
            }
            if (sId.startsWith("#")) {
                sId = sId.substring(1);
            }
            int ideaId;
            try {
                ideaId = Integer.parseInt(sId);
            }
            catch (NumberFormatException ex) {
                throw new SyntaxException();
            }
            Idea idea = ideasById.get(ideaId);
            if (idea == null) {
                player.sendText(true, "Invalid idea number: " + ideaId);
                return;
            }
            if (idea.votes.contains(player.getId())) {
                player.sendText(true, "You have already voted for that idea.");
                return;
            }
            ideasSorted.remove(idea);
            idea.votes.add(player.getId());
            ideasSorted.add(idea); // resort
            player.sendText(true, "Your vote in favor of idea #" + idea.ideaId + " has been recorded.");
        }
        else if (subcmd.equals("unvote")) {
            String sId = CommandProcessor.nextToken();
            if (sId == null || CommandProcessor.nextToken() != null) {
                throw new SyntaxException();
            }
            if (sId.startsWith("#")) {
                sId = sId.substring(1);
            }
            int ideaId;
            try {
                ideaId = Integer.parseInt(sId);
            }
            catch (NumberFormatException ex) {
                throw new SyntaxException();
            }
            Idea idea = ideasById.get(ideaId);
            if (idea == null) {
                player.sendText(true, "Invalid idea number: " + ideaId);
                return;
            }
            if (!idea.votes.contains(player.getId())) {
                player.sendText(true, "You haven't voted for that idea.");
                return;
            }
            ideasSorted.remove(idea);
            idea.votes.remove(player.getId());
            ideasSorted.add(idea); // resort
            player.sendText(true, "Your vote in favor of idea #" + idea.ideaId + " has been removed.");
        }
        else {
            throw new SyntaxException();
        }
    }

    @Override
    public String[] getSyntax(Player player) {
        return new String[] {
                "LIST",
                "SUBMIT <description>",
                "VOTE <number>",
                "UNVOTE <number>",
                "DELETE <number>",
        };
    }
    
    private static int getFreeId() {
        return nextId++;
    }

    public static void doSave(ObjectOutputStream oos) throws IOException {
        oos.writeInt(1); // protocol version
        oos.writeObject(ideasById);
    }
    
    public static void doLoad(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.readInt(); // protocol version
        Map<?,?> ideas = (Map<?,?>) ois.readObject(); 
        Iterator<?> iter = ideas.values().iterator();
        while (iter.hasNext()) {
            Idea idea = (Idea) iter.next();
            nextId = Math.max(nextId, idea.ideaId + 1);
            ideasById.put(idea.ideaId, idea);
            ideasSorted.add(idea);
        }
    }

    private static class Idea implements Comparable<Idea>, Externalizable {
        
        private static final long serialVersionUID = -5272756720705232111L;
        
        public int ideaId;
        public int player;
        public String description;
        public Set<Integer> votes = new HashSet<Integer>();
        
        public Idea() {
            // for deserialization
        }
        
        public Idea(int player, String description) {
            this.ideaId = getFreeId();
            this.player = player;
            this.description = description.intern();
        }

        public int compareTo(Idea idea) {
            int comp = new Integer(votes.size()).compareTo(new Integer(idea.votes.size()));
            if (comp == 0) {
                comp = - new Integer(ideaId).compareTo(new Integer(idea.ideaId));
            }
            return comp;
        }

        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeInt(1); // protocol version
            out.writeInt(ideaId);
            out.writeInt(player);
            out.writeObject(description);
            out.writeObject(votes);
        }

        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            in.readInt(); // protocol version
            ideaId = in.readInt();
            player = in.readInt();
            description = (String) in.readObject();
            votes = CommonUtils.uncheckedCast(in.readObject());
        }

    }

}