/*
 *  RPGeneration - A role playing utility generate interesting things
 *  Copyright (C) 2002 Devon D Jones
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  The author of this program grants you the ability to use this code
 *  in conjunction with code that is covered under the Open Gaming License
 *
 *
 */
package org.evilsoft.gaming.RPGeneration;

import org.evilsoft.gaming.Dice;
import java.util.*;
import org.jdom.*;

public class Trait extends ArrayList {
    private VariableHashMap allVars;

    private String holderType = "";
    private String trait = "";
    private String type = "";
    private String value = "";
    private String selectModifier = "0";
    private String usage = "Private";
    private String choose;
    private Roll roll;
    private boolean print = true;
    private boolean each = false;
	private boolean infoConvert = false;

    private ArrayList information = new ArrayList();
    private ArrayList select = new ArrayList();
    private ArrayList operations = new ArrayList();
    private TableCheck tableCheck;

    private String chance = "1";

    private String sides = "0";
    private String num = "0";
    private String bonus = "0";
    private String min = "0";
    private String max = "0";
    private String absolute = "true";

    private Dice die;

    private boolean DEBUG = false;


    public Trait(Element traitElement, VariableHashMap allVars) {
        this.allVars = allVars;
        String test = traitElement.getName();
        if(test.equals("Trait")) {
            handleTrait(traitElement);
            holderType = "Trait";
        }
        else if(test.equals("Detail")) {
            handleDetail(traitElement);
            holderType = "Detail";
        }
        else if(test.equals("Table")) {
            handleDetail(traitElement);
            holderType = "Table";
        }
    }

    private void handleTrait(Element traitElement) {
        try {
            trait = traitElement.getAttribute("Name").getValue();
            sides = traitElement.getAttribute("Die").getValue();
            num = traitElement.getAttribute("Num").getValue();
            bonus = traitElement.getAttribute("Bonus").getValue();
            min = traitElement.getAttribute("Min").getValue();
            max = traitElement.getAttribute("Max").getValue();
            if(traitElement.getAttribute("SelectModifier") != null) {
                selectModifier = traitElement.getAttribute("SelectModifier").getValue();
            }
            if(traitElement.getAttribute("Choose") != null) {
                choose = traitElement.getAttribute("Choose").getValue();
            }
            absolute = traitElement.getAttribute("Absolute").getValue();
            List details = traitElement.getChildren("Detail");
            ListIterator elementIterator = details.listIterator();
            while(elementIterator.hasNext()) {
                Element detailsElement = (Element)elementIterator.next();
                Trait detail = new Trait(detailsElement, allVars);
                add(detail);
            }

            Element noPrint = traitElement.getChild("Noprint");
            if(noPrint != null) {
                this.print = false;
            }

            Element each = traitElement.getChild("Each");
            if(each != null) {
                this.each = true;
            }

            List varMods = traitElement.getChildren("VarMod");
            ListIterator varModsIterator = varMods.listIterator();
            while(varModsIterator.hasNext()) {
                Element varModElement = (Element)varModsIterator.next();
                operations.add(new Operation(varModElement));
            }
            Element tabChk = traitElement.getChild("TableCheck");
            if(tabChk != null) {
                this.tableCheck = new TableCheck(tabChk, allVars);
            }
        }
        catch(Exception e) {
            System.out.println("XML ERROR");
            e.printStackTrace();
        }
    }

    private void handleDetail(Element detailsElement) {
        try {
            if(detailsElement.getAttribute("Type") != null) {
                type = detailsElement.getAttribute("Type").getValue();
            }
            if(detailsElement.getAttribute("Name") != null) {
				System.out.println("Attribute 'Name' in tags Details/Table is depricated, use 'Value' instead : " + type + " " + value);
            }
            if(detailsElement.getAttribute("Value") != null) {
                value = detailsElement.getAttribute("Value").getValue();
            }
            if(detailsElement.getAttribute("Chance") != null) {
                chance = detailsElement.getAttribute("Chance").getValue();
            }
            if(detailsElement.getAttribute("Usage") != null) {
                usage = detailsElement.getAttribute("Usage").getValue();
            }
            if(detailsElement.getAttribute("InfoConvert") != null) {
                infoConvert = detailsElement.getAttribute("InfoConvert").getBooleanValue();
            }

            if(detailsElement.getAttribute("SelectModifier") != null) {
                selectModifier = detailsElement.getAttribute("SelectModifier").getValue();
            }

            if(detailsElement.getAttribute("Choose") != null) {
                choose = detailsElement.getAttribute("Choose").getValue();
            }

            List informationList = detailsElement.getChildren("Information");
            ListIterator informationIterator = informationList.listIterator();
            while(informationIterator.hasNext()) {
                Element informationElement = (Element)informationIterator.next();
                information.add(new Information(informationElement, allVars));
            }

            List selectList = detailsElement.getChildren("Select");
            ListIterator selectIterator = selectList.listIterator();
            while(selectIterator.hasNext()) {
                Element selectElement = (Element)selectIterator.next();
                select.add(new Select(selectElement, allVars));
            }

            Element roll = detailsElement.getChild("Roll");
            if(roll != null) {
                this.roll = new Roll(roll, type, allVars);
            }

            Element noPrint = detailsElement.getChild("Noprint");
            if(noPrint != null) {
                this.print = false;
            }

            Element each = detailsElement.getChild("Each");
            if(each != null) {
                this.each = true;
            }

            List details = detailsElement.getChildren("Detail");
            ListIterator elementIterator = details.listIterator();
            while(elementIterator.hasNext()) {
                Element childDetailsElement = (Element)elementIterator.next();
                Trait detail = new Trait(childDetailsElement, allVars);
                add(detail);
            }

            List varMods = detailsElement.getChildren("VarMod");
            ListIterator varModsIterator = varMods.listIterator();
            while(varModsIterator.hasNext()) {
                Element varModElement = (Element)varModsIterator.next();
                operations.add(new Operation(varModElement));
            }
            Element tabChk = detailsElement.getChild("TableCheck");
            if(tabChk != null) {
                this.tableCheck = new TableCheck(tabChk, allVars);
            }
        }
        catch(Exception e) {
            System.out.println("XML ERROR");
            e.printStackTrace();
        }
    }

    private int getSides() {
        return new Integer(allVars.parse(sides)).intValue();
    }

    private int getNum() {
        return new Integer(allVars.parse(num)).intValue();
    }

    private int getBonus() {
        return new Integer(allVars.parse(bonus)).intValue();
    }

    private int getMin() {
        return new Integer(allVars.parse(min)).intValue();
    }

    private int getMax() {
        return new Integer(allVars.parse(max)).intValue();
    }

    private boolean getAbsolute() {
        return new Boolean(allVars.parse(absolute)).booleanValue();
    }

    public String getName() {
        return allVars.parse(trait);
    }

    public String getUsage() {
        return allVars.parse(usage);
    }

    public int getChances() {
        return new Integer(allVars.parse(chance)).intValue();
    }

    public String getType() {
        return allVars.parse(type);
    }

    public String getData() {
        return allVars.parse(value);
    }

    public ArrayList getSelections() {
        ArrayList informationList = new ArrayList();
        if(select.size() > 0) {
            for(int i = 0; i < select.size(); i++) {
                Select selection = (Select)select.get(i);
                try {
                    informationList.addAll(selection.getSelection());
                }
                catch(variableException e) {
                    System.out.println(e.getMessage());
                }
            }
        }

        return informationList;
    }

    private int getNumTraits() {
        int rMax = getMax();
        int rMin = getMin();
        int rNum = getNum();
        int rSides = getSides();
        int rBonus = getBonus();
        int numTraits;
        boolean rAbsolute = getAbsolute();

        if(rMax <= 0) {
            numTraits = 0;
        }
        else {
            this.die = new Dice(rNum, rSides, rBonus, allVars.getRandom());

            if(rAbsolute) {
                numTraits = Math.abs(die.roll());
            }
            else {
                numTraits = die.roll();
            }
            while(numTraits < rMin || numTraits > rMax) {
                if(rAbsolute) {
                    numTraits = Math.abs(die.roll());
                }
                else {
                    numTraits = die.roll();
                }
            }
        }
        return numTraits;
    }

    public ArrayList getTraits() {
        if(DEBUG) {System.out.println("Trait: " + trait);}

        ArrayList traits = new ArrayList();
        int numTraits = 0;
        numTraits = getNumTraits();
        if(numTraits == 0) {
            return null;
        }

        int breakFor = 0;
        for(int i = 0; i < numTraits; i++) {
            Detail oneTrait = getDetail();
            oneTrait.switchPrintable(print);
            if(oneTrait != null) {
                traits.add(oneTrait);
            }
            else {
                breakFor++;
                if(breakFor < 3) {
                    i--;
                }
                else {
                    breakFor = 0;
                }
            }
        }
        return traits;
    }

    public Detail getDetail() {
		//Do any operations for this table/detail
        try {
            allVars.doOperation(operations);
        }
        catch(variableException e) {
            System.out.println(e.getMessage());
        }
		
		//Set the value, and do any replacements from a roll
        String modValue = value;
        if(roll != null) {
            int rollTotal = roll.getRoll(allVars.getRandom());
            modValue = modValue.replaceAll("#", rollTotal + "");
        }
		
		if(this.size() > 0) { //If this table/detail has any children, recurse through them
            //Get the detail
            Detail currDetails = null;
            if(each) { // If there is an each tag, every detail/table tag must be iterated through
                boolean firstIteration = true;
                for(int i = 0; i < this.size(); i++) {
                    Trait chkDetail = (Trait)get(i);
                    if(chkDetail.getChances() > 0) {
                        Detail itrDetails = chkDetail.getDetail();
                        if(firstIteration) {
                            firstIteration = false;
                            currDetails = itrDetails;
                        }
                        else {
                            //currDetails.addInformation(new Information(itrDetails.getType(), itrDetails.getTrait(), allVars));
                            currDetails.addInformation(itrDetails.getInformation(true));
                        }
                    }
                }
            }
            else { // otherwise, select one randomly or via a choice attribute
                //Determine the total die roll range (from 0 to rangeTop)
                int rangeTop = 0;
                for(int i = 0; i < this.size(); i++) {
                    Trait detail = (Trait)get(i);
                    rangeTop = rangeTop + detail.getChances();
                }
                
                if(rangeTop <= 0) { //the die will nullpointer if it is not at least 1
                    rangeTop = 1;
                }
                
                // Determine which entry to choose
                int choice;
                if(choose == null) { //if the choice is not predetermined, select it by die roll (and modify it by any selectmods)
                    //Check to see if there is a modifier to the roll for this selection
                    int iEffect = new Integer(allVars.parse(selectModifier)).intValue();
                    
                    Dice traitDie = new Dice(1, rangeTop, 0, allVars.getRandom());
                    choice = traitDie.roll();
                    choice = choice + iEffect;
                }
                else { //otherwise, choose the predetermined value
                    choice = new Integer(allVars.parse(choose)).intValue();
                }
                
                //make sure the choice is properly within range
                if(choice < 0) { //Choice cannot be negative
                    choice = 0;
                }
                else if(choice > rangeTop) { //Choice cannot be greater then the range top.
                    choice = rangeTop;
                }
                
                //select the detail to return
                int chance = 0;
                //Iterate through the list of choices until the chances (from each detail) are greater the the num chosen as the 'choice'
                for(int i = 0; i < this.size(); i++) {
                    Trait chkDetail = (Trait)get(i);
                    int effectChances = chkDetail.getChances();
                    if(effectChances > 0) {
                        chance = chance + effectChances;
                        if(chance >= choice) {
                            currDetails = chkDetail.getDetail();
                            if(currDetails != null) {
                                currDetails.switchPrintable(print);
                            }
                            break;
                        }
                    }
                }
            }
            
            // get any selections, and return the detail
            currDetails.addInformation(getSelections());
            return currDetails;
        }
		//If not, check to see if this has a table call as a child
        else if(tableCheck != null) {
            Detail retDetail = tableCheck.getDetail();
            try {
				if(retDetail != null) {
					allVars.doOperation(tableCheck.getOperations());
				}
            }
            catch(variableException e) {
                System.out.println(e.getMessage());
            }
            return retDetail;
        }
		//If not, then this is the lowest level - return this detail as the primary one
        else {
			//Create the detail object, get any selections, and return it.
            Detail retDetail = new Detail(type, modValue, information, print, infoConvert, allVars);
            retDetail.addInformation(getSelections());
            return retDetail;
        }
    }
	
    public String toString() {
        return getName();
    }
}

