/********************************************************************

Module: TiAbilityTree

Author: Jon Wiesman

Description:

Class that handles dependencies for the ability tree.

Copyright 2006 Sony Online Entertainment.  All rights reserved.

*********************************************************************/

#include "TiAbilityTree.h"
#include "SyAssert.h"
#include "SyString.h"
#include "SyPathname.h"
#include "SyFile.h"
#include "SyEndian.h"
#include "SyResourceID.h"

TiAbilityTreeNode::TiAbilityTreeNode()
{
  m_uAbilityHash = 0;
  m_nMinLevel = 0;
  m_eFlags = ATN_BLANK;
  m_ptPos.x = m_ptPos.y = 0;
}



TiAbilityTree::TiAbilityTree()
{
  Reset();
}

void TiAbilityTree::Reset()
{
  TiPoint pt;
  for(pt.y = 0; pt.y < c_nMaxATRows; pt.y++)
  {
    for(pt.x = 0; pt.x < c_nMaxATCols; pt.x++)
    {
      GetNode(pt)->m_ptPos = pt;
      GetNode(pt)->SetFlags(ATN_BLANK);
      GetNode(pt)->SetAbilityHash(0);
      GetNode(pt)->SetMinLevel(0);
    }
  }
}

bool TiAbilityTree::IsNodeDependentOn(TiPoint pt1, TiPoint pt2) const
{
  return IsNodeDependentOn(GetNode(pt1), GetNode(pt2));
}


bool TiAbilityTree::IsNodeDependentOn(const TiAbilityTreeNode *pNode1, const TiAbilityTreeNode *pNode2) const
{
  if(pNode1 == NULL || pNode2 == NULL)
    return false;

  if(!pNode2->IsAbility())
    return false;
  if(!pNode1->IsAbility())
    return false;

  TiPoint pt1 = pNode1->m_ptPos;
  TiPoint pt2 = pNode2->m_ptPos;

  if(pt2.y >= pt1.y)
    return false;

  TiPoint pt = pt1;
  pt.y--;

  bool bCameFromLeft = false;
  bool bCameFromRight = false;
  while(pt.y >= 0)
  {
    if(pt == pt2)
      return true;

    const TiAbilityTreeNode *pNode = GetNode(pt);
    if(pNode->IsAbility())
    {
      // fail, because if this were the target node, we'd have returned true by now
      return false;
    }

    if(pt2.x == pt.x)
    {
      if((pNode->HasConnector(ATN_FROM_TOP) != 0)
        || (pNode->HasConnector(ATN_TO_LEFT) != 0 && bCameFromLeft)
        || (pNode->HasConnector(ATN_TO_RIGHT) != 0 && bCameFromRight)
        )
      {
        bCameFromRight = bCameFromLeft = false;
        pt.y--;
      }
      else
      {
        return false;
      }
    }
    else if(pt2.x < pt.x)
    {
      if((pNode->HasConnector(ATN_FROM_LEFT) && !bCameFromRight)
        || pNode->HasConnector(ATN_HORZBAR) && bCameFromRight)
      {
        bCameFromRight = true;
        bCameFromLeft = false;
        pt.x--;
      }
      else
      {
        return false;
      }
    }
    else if(pt2.x > pt.x)
    {
      if((pNode->HasConnector(ATN_FROM_RIGHT) && !bCameFromLeft) 
        || pNode->HasConnector(ATN_HORZBAR) && bCameFromLeft)
      {
        bCameFromLeft = true;
        bCameFromRight = false;
        pt.x++;
      }
      else
      {
        return false;
      }
    }
  }

  return false;
}

const TiAbilityTreeNode *TiAbilityTree::GetNode(TiPoint pt) const
{
  if(pt.x < 0 || pt.x >= c_nMaxATCols || pt.y < 0 || pt.y >= c_nMaxATRows)
    return NULL;
  return &m_arNodes[pt.y * c_nMaxATCols + pt.x];
}

TiAbilityTreeNode *TiAbilityTree::GetNode(TiPoint pt) 
{
  if(pt.x < 0 || pt.x >= c_nMaxATCols || pt.y < 0 || pt.y >= c_nMaxATRows)
    return NULL;
  return &m_arNodes[pt.y * c_nMaxATCols + pt.x];
}


void TiAbilityTree::FillTestTree(TiAbilityTree &tree)
{
  tree.GetNode(TiPoint(0, 0))->SetAbility();
  tree.GetNode(TiPoint(2, 0))->SetAbility();
  tree.GetNode(TiPoint(4, 0))->SetAbility();

  tree.GetNode(TiPoint(0, 1))->AddConnector(ATN_FROM_TOP | ATN_FROM_RIGHT);
  tree.GetNode(TiPoint(1, 1))->AddConnector(ATN_HORZBAR);
  tree.GetNode(TiPoint(2, 1))->AddConnector(ATN_FROM_TOP | ATN_TO_LEFT | ATN_TO_RIGHT);
  tree.GetNode(TiPoint(3, 1))->AddConnector(ATN_HORZBAR);
  tree.GetNode(TiPoint(4, 1))->AddConnector(ATN_FROM_TOP | ATN_FROM_LEFT);

  tree.GetNode(TiPoint(0, 2))->SetAbility();
  tree.GetNode(TiPoint(2, 2))->SetAbility();
  tree.GetNode(TiPoint(4, 2))->SetAbility();

  tree.GetNode(TiPoint(0, 3))->AddConnector(ATN_VERTBAR);
  tree.GetNode(TiPoint(1, 3))->AddConnector(ATN_FROM_RIGHT);
  tree.GetNode(TiPoint(2, 3))->AddConnector(ATN_TO_LEFT | ATN_TO_RIGHT);
  tree.GetNode(TiPoint(3, 3))->AddConnector(ATN_FROM_LEFT);
  tree.GetNode(TiPoint(4, 3))->AddConnector(ATN_VERTBAR);

  tree.GetNode(TiPoint(0, 4))->AddConnector(ATN_VERTBAR);
  tree.GetNode(TiPoint(1, 4))->SetAbility();
  tree.GetNode(TiPoint(3, 4))->SetAbility();
  tree.GetNode(TiPoint(4, 4))->AddConnector(ATN_VERTBAR);

  tree.GetNode(TiPoint(0, 5))->AddConnector(ATN_VERTBAR);
  tree.GetNode(TiPoint(1, 5))->AddConnector(ATN_VERTBAR);
  tree.GetNode(TiPoint(3, 5))->AddConnector(ATN_VERTBAR);
  tree.GetNode(TiPoint(4, 5))->AddConnector(ATN_VERTBAR);

  tree.GetNode(TiPoint(0, 6))->SetAbility();
  tree.GetNode(TiPoint(1, 6))->AddConnector(ATN_VERTBAR);
  tree.GetNode(TiPoint(3, 6))->AddConnector(ATN_VERTBAR);
  tree.GetNode(TiPoint(4, 6))->SetAbility();

  tree.GetNode(TiPoint(0, 7))->AddConnector(ATN_VERTBAR);
  tree.GetNode(TiPoint(1, 7))->AddConnector(ATN_VERTBAR);
  tree.GetNode(TiPoint(3, 7))->AddConnector(ATN_VERTBAR);
  tree.GetNode(TiPoint(4, 7))->AddConnector(ATN_VERTBAR);

  tree.GetNode(TiPoint(0, 8))->AddConnector(ATN_VERTBAR);
  tree.GetNode(TiPoint(1, 8))->SetAbility();
  tree.GetNode(TiPoint(3, 8))->SetAbility();
  tree.GetNode(TiPoint(4, 8))->AddConnector(ATN_VERTBAR);

  tree.GetNode(TiPoint(0, 9))->AddConnector(ATN_VERTBAR);
  tree.GetNode(TiPoint(4, 9))->AddConnector(ATN_VERTBAR);

  tree.GetNode(TiPoint(0, 10))->SetAbility();
  tree.GetNode(TiPoint(4, 10))->SetAbility();

}

bool TiAbilityTree::UnitTest()
{
  TiAbilityTree tree;

  FillTestTree(tree);

  SyAssert(tree.IsNodeDependentOn(TiPoint(0, 2), TiPoint(2, 0)));
  SyAssert(tree.IsNodeDependentOn(TiPoint(0, 2), TiPoint(0, 0)));
  SyAssert(tree.IsNodeDependentOn(TiPoint(2, 2), TiPoint(2, 0)));
  SyAssert(tree.IsNodeDependentOn(TiPoint(4, 2), TiPoint(2, 0)));
  SyAssert(tree.IsNodeDependentOn(TiPoint(4, 2), TiPoint(4, 0)));

  SyAssert(tree.IsNodeDependentOn(TiPoint(1, 4), TiPoint(2, 2)));
  SyAssert(tree.IsNodeDependentOn(TiPoint(3, 4), TiPoint(2, 2)));

  return true;
}

bool TiAbilityTree::MakeConnection(TiPoint pt1, TiPoint pt2)
{
  if(pt1.y == pt2.y)
    return false;

  if(pt1.y > pt2.y)
  {
    TiPoint pt = pt1;
    pt1 = pt2;
    pt2 = pt;
  }

  if(IsNodeDependentOn(pt2, pt1))
  {
    // nothing to do
    return true;
  }

  const int c_nMaxPath = c_nMaxATRows;
  TiPoint arPath[c_nMaxPath];
  int arAdditions[c_nMaxPath];
  int nNumNodes = 0;
  bool bFromRight = false;
  bool bFromLeft = false;

  // always start below the top node
  TiPoint ptNode = pt1;
  ptNode.y++;

  bool bSuccess = false;
  while(!bSuccess)
  {
    if(ptNode == pt2)
    {
      bSuccess = true;
      break;
    }

    if(GetNode(ptNode) == NULL)
      break;

    // if we meet a node along the way, fail
    if(GetNode(ptNode)->IsAbility())
      break;

    if(ptNode.y < pt2.y - 1)
    {
      arAdditions[nNumNodes] = ATN_FROM_TOP;
      arPath[nNumNodes] = ptNode;
      nNumNodes++;
      ptNode.y++;
    }
    else if(ptNode.x < pt2.x)
    {
      // go right!
      if(!bFromLeft)
      {
        arAdditions[nNumNodes] = ATN_TO_RIGHT;
      }
      else
      {
        arAdditions[nNumNodes] = ATN_HORZBAR;
      }
      bFromLeft = true;
      arPath[nNumNodes] = ptNode;
      nNumNodes++;
      ptNode.x++;
    }
    else if(ptNode.x > pt2.x)
    {
      // go left!
      if(!bFromRight)
      {
        arAdditions[nNumNodes] = ATN_TO_LEFT;
      }
      else
      {
        arAdditions[nNumNodes] = ATN_HORZBAR;
      }
      bFromRight = true;
      arPath[nNumNodes] = ptNode;
      nNumNodes++;
      ptNode.x--;
    }
    else // ptNode.x == pt2.x
    {
      if(bFromLeft)
      {
        arAdditions[nNumNodes] = ATN_FROM_LEFT;
        bFromLeft = false;
      }
      else if(bFromRight)
      {
        arAdditions[nNumNodes] = ATN_FROM_RIGHT;
        bFromRight = false;
      }
      else
      {
        arAdditions[nNumNodes] = ATN_FROM_TOP;
      }
      arPath[nNumNodes] = ptNode;
      nNumNodes++;
      ptNode.y++;
    }
  }

  if(bSuccess)
  {
    for(int i = 0; i < nNumNodes; i++)
    {
      GetNode(arPath[i])->AddConnector(arAdditions[i]);
    }
  }
  return bSuccess;
}

void TiAbilityTree::LoadFile(const char8 *pszFile)
{
  Reset();

  if(pszFile == NULL || strlen(pszFile) == 0)
    return;

  // create path name
  SyString strPath = "game_assets/design/ability_trees/";
  strPath += pszFile;
  SyPathname pathname( strPath.AsChar() );

  TiPoint pt;

  SyFile file;
  if(file.Open(pathname.Full().AsChar(), SYFILE_RONLY | SYFILE_BINARY) < 0)
    return;

  const int32 c_nMaxAbilityLength = 128;
  char8 szBuffer[c_nMaxAbilityLength];
  for(pt.y = 0; pt.y < c_nMaxATRows; pt.y++)
  {
    for(pt.x = 0; pt.x < c_nMaxATCols; pt.x++)
    {
      uint32 uFlags = 0;
      int32 nLevel = 0;
      int32 nLength;

      file.Read(&uFlags, sizeof(uFlags));
      SyLEndian(uFlags);
      GetNode(pt)->SetFlags(uFlags);
      file.Read(&nLevel, sizeof(nLevel));
      SyLEndian(nLevel);
      GetNode(pt)->SetMinLevel(nLevel);
      file.Read(&nLength, sizeof(nLength));
      SyLEndian(nLength);
      SyAssert(nLength < c_nMaxAbilityLength);
      szBuffer[0] = 0;
      for(int32 i = 0; i < nLength; i++)
      {
        file.Read(&szBuffer[i], sizeof(char8));
        szBuffer[i + 1] = 0;
      }
      GetNode(pt)->SetAbilityHash(SyHashResourceID(szBuffer));
    }
  }
}

