content: package rabbitescape.render;
  
  import java.io.BufferedReader;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  
  import static rabbitescape.engine.util.Util.*;
  
  import rabbitescape.engine.err.RabbitEscapeException;
  import rabbitescape.engine.util.Util;
  
  public class AnimationLoader
  {
      public static final String NONE = "<none>";
  
      public static class AnimationNotFound extends RabbitEscapeException
      {
          private static final long serialVersionUID = 1L;
  
          public final String name;
  
          public AnimationNotFound( String name )
          {
              this.name = name;
          }
      }
  
      public static class BadAnimationLine extends RabbitEscapeException
      {
          private static final long serialVersionUID = 1L;
  
          public final String line;
  
          public BadAnimationLine( String line )
          {
              this.line = line;
          }
  
          public BadAnimationLine( String line, Throwable cause )
          {
              super( cause );
              this.line = line;
          }
      }
  
      public static class ErrorLoadingAnimationNames extends RabbitEscapeException
      {
          private static final long serialVersionUID = 1L;
  
          public ErrorLoadingAnimationNames( Throwable cause )
          {
              super( cause );
          }
      }
  
      public static String[] animationFilesInResource( String lsResourceName )
      {
          try
          {
              return stringArray(
                  chain(
                      Arrays.asList( NONE ),
                      map(
                          stripLast( 4 ),
                          filter(
                              endsWith( ".rea" ),
                              resourceLines( lsResourceName )
                          )
                      )
                  )
              );
          }
          catch ( ReadingResourceFailed e )
          {
              throw new ErrorLoadingAnimationNames( e );
          }
      }
  
      public static String[] listAll()
      {
          return animationFilesInResource(
              "/rabbitescape/render/animations/ls.txt" );
      }
  
      public static Animation load( String name )
      {
          try
          {
              String key = "/rabbitescape/render/animations/" + name + ".rea";
  
              URL url = AnimationLoader.class.getResource( key );
              if ( url == null )
              {
                  throw new AnimationNotFound( name );
              }
              InputStream stream = url.openStream();
  
              return readAnimation( stream );
          }
          catch ( IOException e )
          {
              throw new AnimationNotFound( name );
          }
      }
  
  
      private static int xOffset = 0, yOffset = 0;
  
      public static Animation readAnimation( InputStream stream )
          throws IOException
      {
          BufferedReader reader = new BufferedReader(
              new InputStreamReader( stream ) );
  
          List<Frame> ret = new ArrayList<>();
          String ln;
          xOffset = 0; yOffset = 0;
          while ( ( ln = reader.readLine() ) != null )
          {
              String trimmedLn = ln.trim();
              if ( !Util.isEmpty( trimmedLn ) )
              {
                  Frame lineFrame = frameFromLine( trimmedLn );
                  if ( null != lineFrame ) {
                      ret.add( lineFrame );
                  }
              }
          }
  
          return new Animation( ret );
      }
  
      private static Frame frameFromLine( String animLine )
      {
          String[] parts = animLine.split( " +" );
  
          try
          {
              switch ( parts.length )
              {
                  case 1:
                  {
                      return new Frame( parts[0], xOffset, yOffset, null );
                  }
                  case 2:
                  {
                      return new Frame(
                          parts[0], Integer.valueOf( parts[1] ) + xOffset,
                          yOffset, null );
                  }
                  case 3:
                  {
                      if ( parts[0].equals( "offset" ) )
                      {
                          xOffset = Integer.valueOf( parts[1] ) + xOffset;
                          yOffset = Integer.valueOf( parts[2] ) + yOffset;
                          return null;
                      }
                      return new Frame(
                          parts[0],
                          Integer.valueOf( parts[1] ) + xOffset,
                          Integer.valueOf( parts[2] ) + yOffset,
                          null
                      );
                  }
                  case 4:
                  {
                      return new Frame(
                          parts[0],
                          Integer.valueOf( parts[1] ) + xOffset,
                          Integer.valueOf( parts[2] ) + yOffset,
                          parts[3]
                      );
                  }
                  default:
                  {
                      throw new BadAnimationLine( animLine );
                  }
              }
          }
          catch ( NumberFormatException e )
          {
              throw new BadAnimationLine( animLine, e );
          }
      }
  }
  