package CMakeWorkspaceCreator;

# ************************************************************
# Description   : A CMake Workspace creator
# Author        : Chad Elliott
# Create Date   : 10/10/2022
# ************************************************************

# ************************************************************
# Pragmas
# ************************************************************

use strict;
use File::Basename;

use CMakeProjectCreator;
use WorkspaceCreator;

use vars qw(@ISA);
@ISA = qw(WorkspaceCreator);

# ************************************************************
# Data Section
# ************************************************************

my $version = '3.12.0';

# ************************************************************
# Subroutine Section
# ************************************************************

sub workspace_per_project {
  #my $self = shift;
  return 1;
}

sub workspace_file_name {
  return 'CMakeLists.txt';
}

sub pre_workspace {
  my($self, $fh) = @_;
  my $crlf = $self->crlf();

  $self->print_workspace_comment($fh,
           '# CMake Workspace', $crlf,
           '#', $crlf,
           '# This file was generated by MPC.', $crlf,
           '#', $crlf,
           '# MPC Command:', $crlf,
           '# ', $self->create_command_line_string($0, @ARGV), $crlf, $crlf);
}

sub out_of_tree {
  my($self, $dir) = @_;
  return ($dir =~ /^\.\.\// || !$self->path_is_relative($dir));
}

## Get the top level directory in the path.  If the path does not contain
## a directory, the path will be returned unmodified.
sub get_top_directory {
  my($self, $path) = @_;

  ## First, convert the path to a relative path based on the current working
  ## directory.
  my $dir = $self->path_to_relative($self->getcwd(), $path);
  if ($self->out_of_tree($dir)) {
    ## If the directory is above the current directory or not relative to
    ## the current working directory, we need to give the directory portion
    ## back and call it a day.
    return $self->mpc_dirname($dir);
  }
  else {
    my $done = 0;
    do {
      ## Go up one directory.  If we were already at the top directory,
      ## we're finished.
      my $next = $self->mpc_dirname($dir);
      if ($next eq '.') {
        $done = 1;
      }
      else {
        $dir = $next;
      }
    } while(!$done);
  }

  return $dir;
}

sub write_ws_top {
  my($self, $fh, $ws) = @_;
  my $crlf = $self->crlf();
  print $fh "cmake_minimum_required(VERSION $version)", $crlf,
            "project($ws CXX)", $crlf;
}

sub write_include_ws {
  my($self, $prjs) = @_;
  my $fh = new FileHandle();
  my $dir = $self->mpc_dirname($$prjs[0]);
  my $file = $dir . '/' . $self->workspace_file_name();

  if (open($fh, ">$file")) {
    my $crlf = $self->crlf();
    $self->pre_workspace($fh);
    $self->write_ws_top($fh, basename($dir));
    foreach my $prj (@$prjs) {
      print $fh "${crlf}include(", basename($prj), ")";
    }
    print $fh $crlf;
    close($fh);
  }
}

sub write_comps {
  my($self, $fh, $creator) = @_;
  my $status = 1;
  my $errorString = '';
  my @project_dirs;
  my @projects = $self->sort_dependencies($self->get_projects(), 0);

  ## Build a list of top level directories.  We only want to go down one
  ## directory.  The workspace in that directory will handle going to
  ## other subdirectories.
  my %dirs;
  my %out_of_tree;
  foreach my $entry (@projects) {
    my $dir = $self->get_top_directory($entry);
    if ($dir ne $entry) {
      if (!exists $dirs{$dir}) {
        ## Keep track of the project existing in this directory
        $dirs{$dir} = 1;

        push(@project_dirs, $dir);
      }

      ## If this directory is out-of-tree, it will not contain a top-level
      ## workspace (due to the way that workspace-per-directory works).  We
      ## need to keep track of it here.
      if ($self->out_of_tree($dir)) {
        if (exists $out_of_tree{$dir}) {
          push(@{$out_of_tree{$dir}}, $entry);
        }
        else {
          $out_of_tree{$dir} = [$entry];
        }
      }
    }
  }

  ## Create the basis of a project so that we can add our add_subdirectory()
  ## calls below it.
  my $crlf = $self->crlf();
  my $ws = TemplateParser::actual_normalize(undef, $self->get_workspace_name());
  $self->write_ws_top($fh, $ws);

  my $first = 1;
  my %bin_used;
  foreach my $dir (@project_dirs) {
    if ($first) {
      $first = undef;
      print $fh $crlf;
    }
    my $bin_dir = '';
    if (exists $out_of_tree{$dir}) {
      ## Because this directory is out-of-tree, CMake requires a binary
      ## directory to be passed to add_subdirectory().
      my $bin = basename($dir);
      while(exists $bin_used{$bin}) {
        $bin .= '_';
      }
      $bin_dir = " $bin";
      $bin_used{$bin} = 1;
      $self->write_include_ws($out_of_tree{$dir});
    }
    print $fh "add_subdirectory($dir$bin_dir)$crlf";
  }

  $first = 1;
  foreach my $entry (@projects) {
    my $dir = $self->mpc_dirname($entry);
    if ($dir eq '.') {
      if ($first) {
        $first = undef;
        print $fh $crlf;
      }
      print $fh "include($entry)$crlf";
    }
  }

  return $status, $errorString;
}

1;
