//////////////////////////////////////////////////////////////////////////////
// 
//                    Copyright 2020, Cornutum Project
//                             www.cornutum.org
//
//////////////////////////////////////////////////////////////////////////////

package org.cornutum.tcases.openapi.testwriter;

import org.cornutum.tcases.io.IndentedWriter;
import org.cornutum.tcases.openapi.resolver.RequestCase;

import static org.apache.commons.io.FilenameUtils.getBaseName;
import static org.apache.commons.io.IOUtils.closeQuietly;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Optional;

/**
 * Writes the source code for a test that executes API requests.
 */
public abstract class TestWriter<S extends TestSource, T extends TestTarget>
  {
  /**
   * Creates a new TestWriter instance.
   */
  protected TestWriter( TestCaseWriter testCaseWriter)
    {
    testCaseWriter_ = testCaseWriter;
    }

  /**
   * Creates a test that executes the request test cases defined by the given {@link TestSource source}
   * and writes the result to the given {@link TestTarget target}.
   */
  public void writeTest( S source, T target)
    {
    String testName =
      getTestName(
        Optional.ofNullable( getTestBaseName( source, target))
        .orElseThrow( () -> new TestWriterException( String.format( "No test name defined by source=%s, target=%s", source, target))));
                    
    File targetFile = getTargetFile( target, testName);

    File targetDir =
      Optional.ofNullable( targetFile)
      .flatMap( file -> Optional.ofNullable( file.getParentFile()))
      .map( File::getAbsoluteFile)
      .orElse( null);
    if( targetDir != null && !(targetDir.exists() || targetDir.mkdirs()))
      {
      throw new TestWriterException( String.format( "Can't create targetDir=%s", targetDir));
      }

    OutputStream fileStream = null;
    IndentedWriter targetWriter = null;
    try
      {
      fileStream =
        targetFile == null
        ? null
        : new FileOutputStream( targetFile);

      OutputStream targetStream =
        Optional.ofNullable( fileStream)
        .orElse(
          Optional.ofNullable( target.getOutput())
          .orElse( System.out));

      targetWriter = new IndentedWriter( targetStream);
      targetWriter.setIndent( 4);

      writeProlog( target, testName, targetWriter);
      writeTestCases( target, testName, source.getRequestCases(), targetWriter);
      writeEpilog( target, testName, targetWriter);
      }
    catch( Exception e)
      {
      throw new TestWriterException( String.format( "Can't write test=%s", testName), e);
      }
    finally
      {
      if( targetWriter != null)
        {
        targetWriter.flush();
        }
      
      closeQuietly( fileStream);
      }
    }

  /**
   * Returns the test name derived from the given base name.
   */
  protected abstract String getTestName( String baseName);

  /**
   * Returns the base test name defined by the given source and target.
   */
  protected String getTestBaseName( S source, T target)
    {
    return
      Optional.ofNullable( target.getName())
      .orElse(
        Optional.ofNullable( target.getFile())
        .map( file -> getBaseName( file.getName()))
        .orElse( source.getApi()));
    }

  /**
   * Returns the target file defined by the given target.
   */
  protected File getTargetFile( T target, String testName)
    {
    File targetFile = target.getFile();
    File targetDir = target.getDir();

    return
      targetFile == null && targetDir == null?
      null :

      targetDir == null?
      targetFile :

      targetFile == null?
      new File( targetDir, testName.replaceAll( "[/<>|:& \\\\]+", "-")) :

      new File( targetDir, targetFile.getName());
    }

  /**
   * Writes the target test prolog to the given stream.
   */
  protected void writeProlog( T target, String testName, IndentedWriter targetWriter)
    {
    writeOpening( target, testName, targetWriter);

    writeDependencies( target, testName, targetWriter);
    getTestCaseWriter().writeDependencies( testName, targetWriter);

    writeDeclarations( target, testName, targetWriter);
    getTestCaseWriter().writeDeclarations( testName, targetWriter);
    }

  /**
   * Writes the target test opening to the given stream.
   */
  protected abstract void writeOpening( T target, String testName, IndentedWriter targetWriter);

  /**
   * Writes the target test dependencies to the given stream.
   */
  protected abstract void writeDependencies( T target, String testName, IndentedWriter targetWriter);

  /**
   * Writes the target test declarations to the given stream.
   */
  protected abstract void writeDeclarations( T target, String testName, IndentedWriter targetWriter);

  /**
   * Writes the target test cases to the given stream.
   */
  protected void writeTestCases( T target, String testName, List<RequestCase> requestCases, IndentedWriter targetWriter)
    {
    requestCases.stream().forEach( requestCase -> writeTestCase( target, testName, requestCase, targetWriter));
    }

  /**
   * Writes a target test case to the given stream.
   */
  protected void writeTestCase( T target, String testName, RequestCase requestCase, IndentedWriter targetWriter)
    {
    getTestCaseWriter().writeTestCase( testName, requestCase, targetWriter);
    }

  /**
   * Writes the target test epilog to the given stream.
   */
  protected void writeEpilog( T target, String testName, IndentedWriter targetWriter)
    {
    getTestCaseWriter().writeClosing( testName, targetWriter);
    writeClosing( target, testName, targetWriter);
    }

  /**
   * Writes the target test closing to the given stream.
   */
  protected abstract void writeClosing( T target, String testName, IndentedWriter targetWriter);

  /**
   * Returns the {@link TestCaseWriter} for this test.
   */
  protected TestCaseWriter getTestCaseWriter()
    {
    return testCaseWriter_;
    }

  private final TestCaseWriter testCaseWriter_;
  }

@@ -1238,22 +1238,16 @@
 stName =
-%0A     
  getTest
@@ -1255,228 +1255,25 @@
 ame(
-%0A        Optional.ofNullable( getTestBaseName( source, target))%0A        .orElseThrow( () -%3E new TestWriterException( String.format( %22No test name defined by source=%25s, target=%25s%22, source, target))));%0A                    
+ source, target);
 %0A   
@@ -2635,32 +2635,661 @@
 %0A      %7D%0A    %7D%0A%0A
+  /**%0A   * Returns the test file written for the given source and target. Returns null if the test is written to a stream.%0A   */%0A  public File getTestFile( S source, T target)%0A    %7B%0A    return getTargetFile( target, getTestName( source, target));%0A    %7D%0A%0A  /**%0A   * Returns the test name derived from the given source and target.%0A   */%0A  protected String getTestName( S source, T target)%0A    %7B%0A    return%0A      getTestName(%0A        Optional.ofNullable( getTestBaseName( source, target))%0A        .orElseThrow( () -%3E new TestWriterException( String.format( %22No test name defined by source=%25s, target=%25s%22, source, target))));%0A    %7D%0A%0A
   /**%0A   * Retur

Add public method=getTestFile