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

package org.cornutum.tcases.openapi.reader;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.parser.ObjectMapperFactory;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import io.swagger.v3.parser.util.OpenAPIDeserializer;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.util.Objects;
import java.util.Optional;
import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * Reads an {@link OpenAPI} object from an Open API Version 3 document.
 */
public class OpenApiReader implements Closeable
  {
  /**
   * Creates a new OpenApiReader instance.
   */
  public OpenApiReader()
    {
    this( (InputStream) null);
    }
  
  /**
   * Creates a new OpenApiReader instance.
   */
  public OpenApiReader( InputStream input)
    {
    this( input, null, null);
    }
  
  /**
   * Creates a new OpenApiReader instance.
   */
  public OpenApiReader( InputStream input, String docType)
    {
    this( input, null, docType);
    }
  
  /**
   * Creates a new OpenApiReader instance.
   */
  public OpenApiReader( InputStream input, URL location)
    {
    this( input, location, null);
    }
  
  /**
   * Creates a new OpenApiReader instance.
   */
  public OpenApiReader( InputStream input, URL location, String defaultDocType)
    {
    this( input == null? null : readerFor( input), location, defaultDocType);
    }
  
  /**
   * Creates a new OpenApiReader instance.
   */
  public OpenApiReader( File input)
    {
    this( input, null);
    }
  
  /**
   * Creates a new OpenApiReader instance.
   */
  public OpenApiReader( File input, String defaultDocType)
    {
    this( inputFor( input), toUrl( input), defaultDocType);
    }
  
  /**
   * Creates a new OpenApiReader instance.
   */
  public OpenApiReader( Reader input)
    {
    this( input, null, null);
    }
  
  /**
   * Creates a new OpenApiReader instance.
   */
  public OpenApiReader( Reader input, String docType)
    {
    this( input, null, docType);
    }
  
  /**
   * Creates a new OpenApiReader instance.
   */
  public OpenApiReader( Reader input, URL location)
    {
    this( input, location, null);
    }
  
  /**
   * Creates a new OpenApiReader instance.
   */
  public OpenApiReader( Reader input, URL location, String defaultDocType)
    {
    reader_ = input;
    location_ = location;

    docType_ =
      docTypeFor( location)
      .orElse( 
        Optional.ofNullable( StringUtils.trimToNull( defaultDocType))
        .map( String::toLowerCase)
        .orElse( "json"));
    }

  /**
   * Returns the location of this document.
   */
  public URL getLocation()
    {
    return location_;
    }

  /**
   * Returns the content type of this document.
   */
  public String getDocType()
    {
    return docType_;
    }

  /**
   * Returns the {@link OpenAPI} instance represented by this document.
   */
  public OpenAPI read()
    {
    JsonNode json;
    
    try
      {
      ObjectMapper mapper =
        isYaml()
        ? ObjectMapperFactory.createYaml()
        : ObjectMapperFactory.createJson();
    
      json = mapper.readTree( getReader());
      }
    catch( Exception e)
      {
      throw new OpenApiReaderException( getLocation(), e);
      }

    SwaggerParseResult parseResult= new OpenAPIDeserializer().deserialize( json, Objects.toString( getLocation(), null));

    Optional.of( parseResult)
      .map( SwaggerParseResult::getMessages)
      .filter( messages -> !messages.isEmpty())
      .map( messages -> new OpenApiReaderException( getLocation(), messages))
      .ifPresent( failure -> { throw failure; });

    return parseResult.getOpenAPI();
    }

  public void close()
    {
    IOUtils.closeQuietly( reader_);
    }

  /**
   * Returns the Reader for this document.
   */
  private Reader getReader()
    {
    return
      reader_ != null
      ? reader_
      : readerFor( System.in);
    }

  /**
   * Returns true if this is a YAML document.
   */
  private boolean isYaml()
    {
    return isYaml( getDocType());
    }

  /**
   * Returns true if this is a YAML extension.
   */
  private static boolean isYaml( String ext)
    {
    return "yml".equals( ext) || "yaml".equals( ext);
    }

  /**
   * Returns a URL for the given file.
   */
  private static URL toUrl( File file)
    {
    try
      {
      return
        file == null
        ? null
        : file.toURI().toURL();
      }
    catch( Exception e)
      {
      throw new IllegalArgumentException( "Can't get URL for file=" + file);
      }
    }

  /**
   * Returns an InputStream for the given file.
   */
  private static InputStream inputFor( File file)
    {
    try
      {
      return
        file == null
        ? null
        : new FileInputStream( file);
      }
    catch( Exception e)
      {
      throw new IllegalArgumentException( "Can't open file=" + file);
      }
    }

  /**
   * Returns a Reader for the given input stream.
   */
  private static Reader readerFor( InputStream input)
    {
    return new InputStreamReader( input, UTF_8);
    }

  /**
   * Returns the content type defined by the given document location.
   */
  private static Optional<String> docTypeFor( URL location)
    {
    return
      Optional.ofNullable( location)
      .flatMap( url -> Optional.ofNullable( StringUtils.trimToNull( FilenameUtils.getExtension( url.getPath()))))
      .map( String::toLowerCase)
      .filter( ext -> "json".equals( ext) || isYaml( ext));
    }

  private Reader reader_;
  private URL location_;
  private String docType_;
  }

@@ -497,16 +497,61 @@
 actory;%0A
+import io.swagger.v3.parser.OpenAPIResolver;%0A
 import i
@@ -652,24 +652,73 @@
 serializer;%0A
+import io.swagger.v3.parser.util.ResolverFully;%0A%0A
 import org.a
@@ -3915,16 +3915,78 @@
      %7D%0A%0A
+    String location = Objects.toString( getLocation(), null);%0A
     Swag
@@ -4062,46 +4062,16 @@
 on, 
-Objects.toString( getLocation(), null)
+location
 );%0A%0A
@@ -4326,22 +4326,29 @@
 );%0A%0A    
-return
+OpenAPI api =
  parseRe
@@ -4362,24 +4362,281 @@
 tOpenAPI();%0A
+    try%0A      %7B%0A      api = new OpenAPIResolver( api, null, location).resolve();%0A      new ResolverFully( false).resolveFully( api);%0A      %7D%0A    catch( Exception e)%0A      %7B%0A      throw new OpenApiReaderException( getLocation(), e);%0A      %7D%0A%0A    return api;%0A
     %7D%0A%0A  pub

Rework using OpenAPIResolver to resolve $ref URIs.