#include "autosort_main_thread.h"
#include "autogroups.h"
#include "autosort_menu.h"
#include <vdr/tools.h>
#include <vdr/plugin.h>


#define SLOWDOWN 10 // ms Time for other Tasks 

cAutoSortMainThread *cAutoSortMainThread::m_Instance = NULL;

cAutoSortMainThread::cAutoSortMainThread()
: cThread("AutoSort: Main")
{
    m_Active = false;
}

cAutoSortMainThread::~cAutoSortMainThread() {
    if (m_Active) Stop();
    //cAutoSortMainThread::m_Instance = NULL;
    m_Instance = NULL;
    delete m_Instance; // valgrind
}

void cAutoSortMainThread::Init(void) {
  if (m_Instance == NULL) {
    m_Instance = new cAutoSortMainThread;
    m_Instance->Start();
    //m_Instance->SetPriority(18);
    }
}

void cAutoSortMainThread::Exit(void) {
    if (m_Instance != NULL) {
	m_Instance->Stop();
	DELETENULL(m_Instance);
//	OpenGroups.Clear();
    }
}


void cAutoSortMainThread::Restart(void) {
  dsyslog("AutoSort - Restart");
  Exit();
  Init();
}


void cAutoSortMainThread::Stop(void) {
    m_Active = false;
    Cancel(3);
}

void cAutoSortMainThread::Action(void) 
{
    OpenGroups.Load();
//    AutoGroups.Load(AddDirectory(cPlugin::ConfigDirectory(), "autosort.conf"), true, true);
    AutoGroups.Load(AddDirectory(ConfigDir, "autosort.conf"), true, true);
    m_Active = true;
    CheckDelimiters();
    LoopChannels();
    while (m_Active) {
      //dsyslog("AutoSort: MainThread ActionLoop");
      // run Group Check if nessasary
      // run CompleteRun if TimeOut or UserRequest
      // run SingleChannelCheck if ChannelChangeSignal caught
      cCondWait::SleepMs(1000);
      };
   OpenGroups.Clear();
}


// --- Main Tasks ------------------------------------------------------------


void cAutoSortMainThread::CheckDelimiters (void)
{
  ASMessage(1, "Checking group delimiters");
  cAutoGroup *group = AutoGroups.GetFirstByOrder();
  char *buf;
  bool foundStart = false;
  int firstGroupChIdx = 1;
  for (cChannel *channel = Channels.First(); (channel && !foundStart); channel = Channels.Get(Channels.GetNextGroup(channel->Index()))) 
    {
    if (strcmp(channel->Name(),group->Name()) == 0) 
      {
      foundStart = true;
      firstGroupChIdx = channel->Index();
      dsyslog("AutoSort: found group '%s' @ Index %d ",group->Name(), channel->Index());
      }
    }
  if (!foundStart){
    // Create all New
    ASMessage(1, "Creating Group delimiters");
    group = AutoGroups.GetFirstByOrder();    
    if (Channels.Lock(true)){
      while (group) {
        AddGroup(group);
        group= AutoGroups.GetNextByOrder(group->OIdx());
        }
    Channels.Unlock();
    }
  } else {
    // Is everybody there?
    ASMessage(1, "Checking for missing group delimiters");
    group = AutoGroups.GetFirstByOrder();    
    // Test for fixed Pos 'Auto Sort'
    if (group->FixedPos() > 0){
      if (group->FixedPos() != Channels.Get(firstGroupChIdx)->Number()){
        dsyslog ("AutoSort: Changing FixedPosition of 'Auto Sort' to %d", group->FixedPos());
        Channels.Get(firstGroupChIdx)->SetNumber(group->FixedPos());
        }
      }
    bool foundNext;
    while ((group = AutoGroups.GetNextByOrder(group->OIdx()))){
      foundNext = false;
      //dsyslog("AutoSort: Searching Group %s", group->Name());
      for (cChannel *channel = Channels.Get(Channels.GetNextGroup(firstGroupChIdx)); (channel && !foundNext); channel = Channels.Get(Channels.GetNextGroup(channel->Index()))) 
        {
        if (!m_Active) return;
        cCondWait::SleepMs(SLOWDOWN);
        if (strcmp(channel->Name(),group->Name()) == 0) 
          {
          foundNext = true;
	  // Test for fixed Pos
	  if (group->FixedPos() > 0){
	    if (group->FixedPos() != channel->Number()){
	      dsyslog ("AutoSort: Changing FixedPosition of '%s' to %d",channel->Name(), group->FixedPos());
	      channel->SetNumber(group->FixedPos());
	      }
	    }	  
          }
	}  
        if (!foundNext) {
	  if (Channels.Lock(true)){
            AddGroup(group);
	    Channels.Unlock();
	    }
	  }
      } 
    // Is anyone to much?    
    ASMessage(1, "Checking for old group delimiters");
    for (cChannel *channel = Channels.Get(Channels.GetNextGroup(firstGroupChIdx)); channel; channel = Channels.Get(Channels.GetNextGroup(channel->Index()))) 
      {
      if (!m_Active) return;
      cCondWait::SleepMs(SLOWDOWN);
      foundNext = false;
      //dsyslog("AutoSort: Found Group %s", channel->Name());
      group = AutoGroups.GetFirstByOrder();    
      while ((group = AutoGroups.GetNextByOrder(group->OIdx()))){
        //dsyslog("AutoSort: Compare with %s",group->Name());
        if (strcmp(channel->Name(),group->Name()) == 0) 
          {
          foundNext = true;
          }	
        }
        if (!foundNext) {
	  if (Channels.Lock(true)){
            int saveIndex = channel->Index();
	    DeleteGroup(channel->Index());
	    channel = Channels.Get(saveIndex-1);
	    Channels.Unlock();
	    }
	  }
      }
    // Check Order
    ASMessage(1, "Checking order of group delimiters");
    group = AutoGroups.GetFirstByOrder();
    cAutoGroup *nextGroup = group;
    cChannel *channel = Channels.Get(group->GetChIndex());

    while ((nextGroup = AutoGroups.GetNextByOrder(group->OIdx()))){
      channel = Channels.Get(Channels.GetNextGroup(group->GetChIndex()));
      if (!m_Active) return;
      cCondWait::SleepMs(SLOWDOWN);
      //dsyslog ("AutoSort: Have %s search %s found %s",group->Name(), nextGroup->Name(), channel->Name());
      if (strcmp(channel->Name(),nextGroup->Name()) == 0){
        group = nextGroup;
      } else {
        if (strcmp(Channels.Get(Channels.GetNextGroup(channel->Index()))->Name(),nextGroup->Name()) == 0){
	  //dsyslog("It's the overnext");
	  if (Channels.Lock(true)){
            MoveGroupToEnd(channel->Index());	            
	    Channels.Unlock();
	    }
	} else {
	  if (Channels.Lock(true)){
            MoveGroupBehindGroup(nextGroup->GetChIndex(),group);
	    Channels.Unlock();
	    }
          } // if overnext
	}
      } //while NextByOrder
     
  } //  if (!foundStart){
  buf=NULL;
  free(buf);
}

void cAutoSortMainThread::PlaceChannel(cChannel *channel)
{
  cAutoGroup *inGroup =  InGroup(channel->Index());
  //dsyslog ("Place Channel '%s' from '%s'",channel->Name(), inGroup->Name());
  for (cAutoGroup *loopGroup = AutoGroups.GetFirstByPrio(); loopGroup; loopGroup = AutoGroups.GetNextByPrio(loopGroup->PIdx())) {
    if (!m_Active) return;
    if ((loopGroup == inGroup) && (inGroup->KeepHere())) break;
    if (loopGroup->IsSpecial()) break;
    if (loopGroup->ChannelFitsGroup(channel->Index())){
	if (inGroup != loopGroup){
	  inGroup->ModifyChannelToGroup(channel->Index(), true);
	  loopGroup->ModifyChannelToGroup(channel->Index(), false);
          MoveChannelToGroup(channel->Index(), loopGroup);
        } else {
          // Channel may have changed it's name but stayed in same group
          Sort(inGroup); // kill this -> make loop-groups
          }
	// Found destination, going out
	inGroup = NULL;
	free(inGroup);
	loopGroup = NULL;
	free(loopGroup);
        return;	
        }
      }
    //Channel dosn't fit anywhere
    if (!inGroup->KeepHere()){
      inGroup->ModifyChannelToGroup(channel->Index(), true);
      cAutoGroup *ToGroup = AutoGroups.GetByName("Unsorted");
      MoveChannelToGroup(channel->Index(), ToGroup);
      ToGroup = NULL;
      free(ToGroup);
      //dsyslog ("PlaceChannel(): Channel moved to 'Unsorted'");
    } else {
      // Channel may have changed it's name but stayed in same group
      Sort(inGroup); // kill this -> make loop-groups
      }
    inGroup = NULL;
    free(inGroup);
}

void cAutoSortMainThread::LoopChannels(void)
{
  startTime = time(NULL);
  cPluginManager::GetPlugin("autosort")->SetupStore("StartTime", startTime);
  Setup.Save();
  int startMark = AutoGroups.GetByName("Auto Sort")->GetChIndex();
  //int startMark = AutoGroups.GetByName("Old Channels")->GetChIndex();
  for (cChannel *channel = Channels.Get(Channels.GetNextNormal(startMark)); channel; channel = Channels.Get(Channels.GetNextNormal(channel->Index()))) {
    if (!m_Active) return;
    cPluginManager::GetPlugin("autosort")->SetupStore("ActiveChannel", activeChannel = channel->Index());
    int channelTime = time(NULL);
    cCondWait::SleepMs(SLOWDOWN);
    if (strcmp(InGroup(channel->Index())->Name(), "New Channels") == 0) break;
//    if (Channels.Lock(true)){
    if (Channels.Lock(false)){  // XXX Not sure what will happen
      cChannel *saveNext = Channels.Next(channel);
      SetTimeStamp(channel->Index());
      PlaceChannel(channel);
      channel = NULL;
      if (saveNext) {
        channel = Channels.Prev(saveNext);
	} else {
	channel = Channels.Last();
	}
      Channels.Unlock();
      saveNext = NULL;
      free(saveNext);
      }
//    dsyslog("AutoSort: ---- channel %d: %s took %d s ----", channel->Number(), channel->Name(), (int) time(NULL) - channelTime);
    }
  if (writeTimeStamp) {
    lastTimeStampSet = time(NULL);

    cMenuTimeStamps *DummyMenu = new cMenuTimeStamps;
    DummyMenu->StoreTime();
    delete(DummyMenu);
    
    }
  lastDuration = time(NULL) - startTime;
  if (averageDuration != 0) {
    averageDuration = (int)rint((lastDuration + averageDuration) / 2 );
  } else {
    averageDuration = lastDuration;
    }
  cPluginManager::GetPlugin("autosort")->SetupStore("LastDuration", lastDuration );
  cPluginManager::GetPlugin("autosort")->SetupStore("AverageDuration", averageDuration );
  cPluginManager::GetPlugin("autosort")->SetupStore("StartTime", startTime = 0);
  cPluginManager::GetPlugin("autosort")->SetupStore("ActiveChannel", activeChannel = 0);
  Setup.Save();
  dsyslog("AutoSort: --- Completed after %d min ---", (int)rint(lastDuration / 60));
}

void cAutoSortMainThread::SetTimeStamp (int ChIndex)
{
  // TimeStamp is in seconds
  // 1 hour   = 3600
  // 1 day    = 86400
  // 1 week   = 604800
  // 1 month  = 2592000
  // 3 months = 7776000
  if (writeTimeStamp) {
    if ( (time(NULL) - timeStampSetDelta) > lastTimeStampSet) {
      cChannel *channel = Channels.Get(ChIndex);
      if (!strstr(channel->ShortName(),"ASTS")){
        char *buf = NULL;
        int stampTime = time(NULL);
        if (strlen(channel->ShortName()) < 1){
	  asprintf(&buf,"%s ASTS-%d", KillSpecial(strdup(channel->Name())), stampTime);
        } else {
	  asprintf(&buf,"%s ASTS-%d", KillSpecial(strdup(channel->ShortName())), stampTime);
	  }	
        channel->SetName(channel->Name(), buf, channel->Provider());
        //free(buf);
      }
    }
  }
}


// --- Group Moving ------------------------------------------------------------


void cAutoSortMainThread::FlushNewChannels (void)
{
  if (Channels.Lock(true)){
    char *buf;  
    asprintf(&buf, "Flushing New Channels");
    ASMessage(2, buf);    
    buf=NULL;
    free(buf);
    cAutoGroup *group = AutoGroups.GetByName("New Channels");
    cChannel *channel = Channels.Get(group->GetChIndex());    
    Channels.Del(channel);
    Channels.ReNumber();
    Channels.SetModified(true);
    cAutoSortMainThread::m_Instance->AddGroup(group);
    delete group;
    channel=NULL;
    free(channel);
    }
}

cAutoGroup *cAutoSortMainThread::InGroup (int ChIndex)
{
  cChannel *channel = Channels.Get(Channels.GetPrevGroup(ChIndex));
  char *name = strdup(channel->Name());
  channel =NULL;
  free(channel);
  return AutoGroups.GetByName(name);  
}

void cAutoSortMainThread::MoveGroupToEnd (int ChIndex)
{
  cChannel *channel1 = Channels.Get(ChIndex);
  cChannel *channel2 = Channels.Get(Channels.GetNextGroup(ChIndex)-1);
  char *buf;  
  asprintf(&buf, "Moving Group %s to end",channel1->Name());
  ASMessage(2, buf);    
  buf=NULL;
  free(buf);
  cAutoGroup *group = new cAutoGroup;
  group->SetName("Dummy");
  AddGroup(group);
  cAutoGroup *group2 = new cAutoGroup;
  group2->SetName("Dummy2");
  AddGroup(group2);
  int i =  channel2->Index();
  while (i != ChIndex -1)
    {
    MoveChannelToGroup(i, group);
    i--;
    }
  channel1 = Channels.Get(group->GetChIndex());    
  Channels.Del(channel1);
  Channels.ReNumber();
  channel1 = Channels.Get(group2->GetChIndex());    
  Channels.Del(channel1);
  Channels.ReNumber();
  Channels.SetModified();
  delete group;
  delete group2;
  channel1=NULL;
  channel2=NULL;
  free(channel1);
  free(channel2);
}

void cAutoSortMainThread::MoveGroupBehindGroup (int ChIndex, cAutoGroup *group)
{
  cChannel *channel1 = Channels.Get(ChIndex);
  cChannel *channel2 = Channels.Get(Channels.GetNextGroup(ChIndex)-1);
  if (!channel2) channel2 = Channels.Last();
  char *buf;  
  asprintf(&buf, "Moving Group '%s' behind '%s'",channel1->Name(),group->Name());
  ASMessage(2, buf);    
  buf=NULL;
  free(buf);
  int NextGroupIdx = Channels.GetNextGroup(group->GetChIndex());
  if (channel1->Index() < AutoGroups.GetFirstByOrder()->GetChIndex() ){
    dsyslog("AutoSort: -ERROR- ::MoveGroupBehindGroup() tries working before Auto-Sort-Mark  -ABORTED-");
    return;
    }	    
  cAutoGroup *dummyGroup = new cAutoGroup;
  dummyGroup->SetName("Dummy");
  InsGroup(dummyGroup,NextGroupIdx);
  ChIndex++;
  cAutoGroup *dummyGroup2 = new cAutoGroup;
  dummyGroup2->SetName("Dummy2");
  InsGroup(dummyGroup2,NextGroupIdx);
  ChIndex++;
  if ( ChIndex == Channels.Last()->Index() ) {
    //dsyslog(" group %s is last and empty",channel1->Name());
    MoveChannelToGroup(ChIndex, dummyGroup);    
    }
  else if ( ChIndex == channel2->Index() ) {
    //dsyslog(" group %s is inbetween and empty",channel1->Name());
    MoveChannelToGroup(ChIndex, dummyGroup);    
    }
  else {    
    //dsyslog(" group %s is inbetween and filled",channel1->Name());
    int i =  channel2->Index();
    // ToDo: Make a heavy speed-up here
    while (Channels.Get(i)->GroupSep() == false) {
      MoveChannelToGroup(i, dummyGroup);
      }
    MoveChannelToGroup(i, dummyGroup);
    } // if
  channel1 = Channels.Get(dummyGroup->GetChIndex());    
  Channels.Del(channel1);
  Channels.ReNumber();
  channel1 = Channels.Get(dummyGroup2->GetChIndex());    
  Channels.Del(channel1);
  Channels.ReNumber();
  Channels.SetModified();
  delete dummyGroup;
  delete dummyGroup2;
  channel1=NULL;
  channel2=NULL;
  free(channel1);
  free(channel2);
}

void cAutoSortMainThread::AddGroup (cAutoGroup *group)
{
  char *buf;  
  asprintf(&buf, "Creating Group '%s'", group->Name());
  ASMessage(2, buf);    
  buf=NULL;
  free(buf);
  cChannel *NewGroup = new cChannel;
  char *name;
  if (group->FixedPos() > 0) {
    asprintf(&name, ":@%d %s",group->FixedPos(),group->Name());
  } else {
    asprintf(&name,":%s",group->Name());
    }
  NewGroup->Parse(name);
  Channels.Add(NewGroup);
  Channels.ReNumber();
  Channels.SetModified();
  name=NULL;
  free(name);
}

void cAutoSortMainThread::InsGroup (cAutoGroup *group, int ChIndex)
{
  if (strcmp(group->Name(),"Dummy") && strcmp(group->Name(),"Dummy2")){
    char *buf;  
    asprintf(&buf, "Inserting Group '%s'", group->Name());
    ASMessage(2, buf);    
    buf=NULL;
    free(buf);
    }
  cChannel *NewGroup = new cChannel;
  cChannel *Before = Channels.Get(ChIndex);
  char *name;
  asprintf(&name,":%s",group->Name());
  NewGroup->Parse(name);
  Channels.Ins(NewGroup,Before);
  Channels.ReNumber();
  Channels.SetModified();
  name=NULL;
  free(name);
  Before=NULL;
  free(name);
}

void cAutoSortMainThread::DeleteGroup (int ChIndex)
{
  //Delete Group means delete the delimiter and move channels to Unsorted
  cChannel *channel1 = Channels.Get(ChIndex);
  cChannel *channel2;
  char *buf;  
  asprintf(&buf, "Deleting Group '%s'",channel1->Name());
  ASMessage(2, buf);    
  buf=NULL;
  free(buf);
  if (Channels.GetNextGroup(ChIndex) > 0){
    channel2 = Channels.Get(Channels.GetNextGroup(ChIndex)-1);
    cAutoGroup *group = AutoGroups.GetByName("Unsorted");
    for (int i = channel2->Index(); i > ChIndex; i--)
      {
      MoveChannelToGroup(i, group);
      }
    group=NULL;
    free(group);
    }
    Channels.Del(channel1);
    Channels.ReNumber();
    Channels.SetModified();  
  channel1=NULL;
  channel2=NULL;
  free(channel1);
  free(channel2);
}

// --- Channel Moving ----------------------------------------------------------


void cAutoSortMainThread::Sort(cAutoGroup *group)
{
  int firstChIndex;
  int lastChIndex;
    if (group->Sort()){
      firstChIndex = group->GetChIndex() + 1;
      if (Channels.GetNextGroup(group->GetChIndex())){ // XXX
        lastChIndex = Channels.GetNextGroup(group->GetChIndex()) - 1;
      } else {
	lastChIndex = Channels.Last()->Index();
	}
      if ((lastChIndex - firstChIndex) > 0){
        bool madeChange = true;
	//while (madeChange){
        //dsyslog("AutoSort: sorting group %s -----------------",group->Name());
	madeChange = false;
	for (cChannel *channel1 = Channels.Get(firstChIndex); channel1->Index() < lastChIndex; channel1 = Channels.Next(channel1)) {
          if (!m_Active) return;
          cCondWait::SleepMs(SLOWDOWN);
	  int ch2Index = channel1->Index() +1;
	  if (versionSorting) {
	    while ( (ch2Index <= lastChIndex) && (strverscmp(UpString(KillSpecial(strdup(channel1->Name()))), UpString(KillSpecial(strdup(Channels.Get(ch2Index)->Name())))) > 0 ) ) {
	      ch2Index++;
	      }
	  } else {
	    while ( (ch2Index <= lastChIndex) && (strcasecmp(channel1->Name(), Channels.Get(ch2Index)->Name()) > 0 ) ) {
	      ch2Index++;
	      }
	    }
	  ch2Index--;
	  if (channel1->Index() != ch2Index ){
	    if (channel1->Index() < AutoGroups.GetFirstByOrder()->GetChIndex() ){
	      dsyslog("AutoSort: -ERROR- ::Sort() trys working before Auto-Sort-Mark  -ABORTED-");
	      return;
	      }	    
	    if (ch2Index != Channels.Last()->Index()) { 
    	      char *buf;
              asprintf(&buf, "Group '%s': Sorting channel '%s' behind '%s'", group->Name(), channel1->Name(), Channels.Get(ch2Index)->Name());
              ASMessage(3, buf);
	      buf = NULL;
	      free(buf);
	      Channels.Move(channel1, Channels.Get(ch2Index));
              Channels.ReNumber();
              Channels.SetModified();
	      madeChange = true;
	      if (strcmp(InGroup(ch2Index)->Name(), group->Name())!=0) {
                dsyslog ("ERROR: (Sort) Moved %s to %s instead of %s ", Channels.Get(ch2Index)->Name(), InGroup(ch2Index)->Name(), group->Name());
                //Dump(ch2Index -3,ch2Index +3);
                }
	      }
            }
	  }
	}
	//}
      }
    group = NULL;
  //dsyslog("AutoSort: Sorting ready.");
}


void cAutoSortMainThread::MoveChannelToGroup (int ChIndex, cAutoGroup *group)
{
  char *buf;
  cChannel *channel = Channels.Get(ChIndex);
  if (group->GetChIndex() == Channels.Last()->Index()){
    // Move channel behind last channel
    cAutoGroup *group2 = new cAutoGroup;
    group2->SetName("Dummy"); // FixMe: There can be two Dummy Groups
    AddGroup(group2);
    cChannel *ToChannel = Channels.Get(group->GetChIndex()+1);
    if (strcmp(group->Name(),"Dummy") != 0){
      asprintf(&buf, "Moving '%s' to '%s'", channel->Name(), group->Name());
      ASMessage(3, buf);
      }
    Channels.Move(channel, ToChannel);
    Channels.ReNumber();
    cChannel *channel1 = Channels.Get(group2->GetChIndex()); // delete dummy
    Channels.Del(channel1);
    Channels.ReNumber();
    Channels.SetModified();
    dsyslog ("AS Debug: Deleting pointer 'group2' in cAutoSortMainThread::MoveChannelToGroup");
    delete group2;
    channel1=NULL;
    free (channel1);
  } else {
    // Move channel anywhere else
    cChannel *ToChannel;
    if (channel->Index() > group->GetChIndex()) {
      ToChannel = Channels.Get(group->GetChIndex()+1);
    } else {
      ToChannel = Channels.Get(group->GetChIndex());
      }
    if (strcmp(group->Name(),"Dummy") != 0){
      asprintf(&buf, "Moving '%s' to '%s'", channel->Name(), group->Name());
      ASMessage(3, buf);
      }
    Channels.Move(channel, ToChannel);
    Channels.ReNumber();
    Channels.SetModified();
    ToChannel=NULL;
    free (ToChannel);
  }
  channel=NULL;
  buf=NULL;
  free (channel);
  free (buf);
  // Sort here
  if (strcmp(group->Name(),"Dummy") != 0 ){
    Sort(group);
    }
}
