﻿// Note - AvidaAttributeNames should be validated before creating Genotypes, that way all keys are found via AvidaAttributeNames.

using UnityEngine;
using System;
using System.Collections.Generic;

/// <summary>
/// Genotypes represent the nodes of a Phylogenetic Tree.
/// </summary>
[Serializable]
public class Genotype : TreeNode
{

    // The attribute values of the Genotype. The indices correspond to AvidaAttribute Columns, starting from 0.
    private readonly List<string> _attribValues;

    // The sample population which the Genotype was born in.
    private readonly Phylogeny _phylogeny;

    private readonly int _id;
    private readonly int _parentID;
    private readonly int _timeBorn;
    private readonly int _timeDeact;
    private readonly int _phenocode;

    /// <summary>
    /// Creates a Genotype and adds itself to the Phylogeny passed.
    /// The colNumsForTasks is used to determine which attributes are tasks via column number.
    /// </summary>
    /// <param name="phylo"></param>
    /// <param name="attribValues"></param>
    /// <param name="taskExecutions"></param>
    public Genotype(Phylogeny phylo, List<string> attribValues, List<int> colNumsForTasks = null)
    {
        _phylogeny = phylo;
        _attribValues = attribValues;

        try 
        {
            _id = int.Parse(GetAttributeValue(MainAttributeNames.ID));

            // First born has no parent.
            if (_id != MainAttributeValues.FirstBornID)
                _parentID = int.Parse(GetAttributeValue(MainAttributeNames.ParentID));

            _timeBorn = GetTimeBorn();
            _timeDeact = GetTimeDeactivated();
            _phenocode = CalculatePhenocode(colNumsForTasks);
        }

        catch (System.FormatException e) 
        {
            Debug.Log("Parsing error when obtaining numeric data for Genotype in Ctor. " + e.Message);
        }

        _phylogeny.Population.Add(_id, this);
    }

    /// <summary>
    /// Obtain the value for important attributes.
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    public string GetAttributeValue(AttribName name)
    {
        if (_phylogeny.GenotypeAttributes.ContainsKey(name.AliasUsed)) {
            int index = _phylogeny.GenotypeAttributes[name.AliasUsed].ColumnNumber;
            return _attribValues[index];
        }

        return null;
    }

    /// <summary>
    /// Get the value for an attribute.
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    public string GetAttributeValue(string name)
    {
        if (_phylogeny.GenotypeAttributes.ContainsKey(name)) {
            int index = _phylogeny.GenotypeAttributes[name].ColumnNumber;
            return _attribValues[index];
        }

        return null;
    }

    // Each list index corresponds to a task the Genotype performed  and the 
    // list values correspond to the number of times that task was executed.
    private int CalculatePhenocode(List<int> colNumsForTasks)
    {
        if (colNumsForTasks == null)
            return NO_PHENOCODE;

        // Get the attribute values for the tasks.
        List<string> taskValues = new List<string>();
        foreach (int col in colNumsForTasks) {
            taskValues.Add(_attribValues[col]);
        }

        // Convert task executions to their numeric form. Sum them all as we go along.
        int totalSum = 0;
        List<int> iTaskValues = new List<int>(taskValues.Capacity);

        foreach (string value in taskValues) {
            int executions = int.Parse(value);
            iTaskValues.Add(executions);
            totalSum += executions;
        }

        // Has a phenocode of 0, no tasks executed.
        if (totalSum == 0)
            return 0;

        int code = 0;

        // Calculate the phenocode.
        for (int i = 0; i < iTaskValues.Count; i++) {

            float ratio = iTaskValues[i] / totalSum;
            if (ratio >= MainAttributeValues.PhenocodeTolerance) {
                code += (int)Math.Pow(2, i+1);
            }
        }
        return code;
    }

    private int GetTimeBorn()
    {
        if (_id == MainAttributeValues.FirstBornID)
            return 0;

        return int.Parse(GetAttributeValue(MainAttributeNames.UpdateBorn));
    }

    private int GetTimeDeactivated()
    {
        string deactivatedStr = GetAttributeValue(MainAttributeNames.UpdateDeactivated);
        if (deactivatedStr == MainAttributeValues.AliveAtCutoff)
            return _phylogeny.CutoffTime;

        return int.Parse(deactivatedStr);
    }

    public int ID
    {
        get { return _id; }
    }

    public int ParentID
    {
        get { return _parentID; }
    }

    public int TimeBorn
    {
        get { return _timeBorn; }
    }

    public int TimeDeactivated
    {
        get { return _timeDeact; }
    }

    public int TimeLived
    {
        get { return _timeDeact - _timeBorn; }
    }

    public int Phenocode
    {
        get { return _phenocode; }
    }

    /// <summary>
    /// The phylogeny that this Genotype belongs in.
    /// </summary>
    public Phylogeny Phylogeny
    {
        get { return _phylogeny; }
    }

    public List<string> AttribValues
    {
        get { return _attribValues; }
    }

    public const int NO_PHENOCODE = -1;
}