/*
 
* Copyright (C) 2011-2015 René Jeschke <rene_jeschke@yahoo.de>
 
*
 
* Licensed under the Apache License, Version 2.0 (the "License");
 
* you may not use this file except in compliance with the License.
 
* You may obtain a copy of the License at
 
*
 
*
     
http://www.apache.org/licenses/LICENSE-2.0
 
*
 
* Unless required by applicable law or agreed to in writing, software
 
* distributed under the License is distributed on an "AS IS" BASIS,
 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
* See the License for the specific language governing permissions and
 
* limitations under the License.
 
*/
package com.github.rjeschke.txtmark;

/**
 
* This class represents a block of lines.
 
*
 
* @author René Jeschke <rene_jeschke@yahoo.de>
 
*/

class Block
{
    
/** This block's type. */
    
public BlockType type
    
= BlockType.NONE;
    
/** Head and tail of linked lines. */
    
public Line
      
lines
   
= null, lineTail = null;
    
/** Head and tail of child blocks. */
    
public Block
     
blocks
  
= null, blockTail = null;
    
/** Next block. */
    
public Block
     
next
    
= null;
    
/** Depth of headline BlockType. */
    
public int
       
hlDepth = 0;
    
/** ID for headlines and list items */
    
public String
    
id
      
= null;
    
/** Block meta information */
    
public String
    
meta
    
= "";

    
/** Constructor. */
    
public Block()
    
{
        
//
    
}

    
/**
     
* @return <code>true</code> if this block contains lines.
     
*/

    
public boolean hasLines()
    
{
        
return this.lines != null;
    
}

    
/**
     
* Removes leading and trailing empty lines.
     
*/

    
public void removeSurroundingEmptyLines()
    
{
        
if (this.lines != null)
        
{
            
this.removeTrailingEmptyLines();
            
this.removeLeadingEmptyLines();
        
}
    
}

    
/**
     
* Sets <code>hlDepth</code> and takes care of '#' chars.
     
*/
    
public void transfromHeadline()
    
{
        
if (this.hlDepth > 0)
        
{
            
return;
        
}
        
int level = 0;
        
final Line line = this.lines;
        
if (line.isEmpty)
        
{
            
return;
        
}
        
int start = line.leading;
        
while (start < line.value.length() && line.value.charAt(start) == '#')
        
{
            
level++;
            
start++;
        
}
        
while (start < line.value.length() && line.value.charAt(start) == ' ')
        
{
            
start++;
        
}
        
if (start >= line.value.length())
        
{
            
line.setEmpty();
        
}
        
else
        
{
            
int end = line.value.length() - line.trailing - 1;
            
while (line.value.charAt(end) == '#')
            
{
                
end--;
            
}
            
while (line.value.charAt(end) == ' ')
            
{
                
end--;
            
}
            
line.value = line.value.substring(start, end + 1);
            
line.leading = line.trailing = 0;
        
}
        
this.hlDepth = Math.min(level, 6);
    
}

    
/**
     
* Used for nested lists. Removes list markers and up to 4 leading spaces.
     
*
     
* @param configuration
     
*
            
txtmark configuration
     
*
     
*/

    
public void removeListIndent(final Configuration configuration)
    
{
        
Line line = this.lines;
        
while (line != null)
        
{
            
if (!line.isEmpty)
            
{
                
switch (line.getLineType(configuration))
                
{
                
case ULIST:
                    
line.value = line.value.substring(line.leading + 2);
                    
break;
                
case OLIST:
                    
line.value = line.value.substring(line.value.indexOf('.') + 2);
                    
break;
                
default:
                    
line.value = line.value.substring(Math.min(line.leading, 4));
                    
break;
                
}
                
line.initLeading();
            
}
            
line = line.next;
        
}
    
}

    
/**
     
* Used for nested block quotes. Removes '>' char.
     
*/

    
public void removeBlockQuotePrefix()
    
{
        
Line line = this.lines;
        
while (line != null)
        
{
            
if (!line.isEmpty)
            
{
                
if (line.value.charAt(line.leading) == '>')
                
{
                    
int rem = line.leading + 1;
                    
if (line.leading + 1 < line.value.length() && line.value.charAt(line.leading + 1) == ' ')
                    
{
                        
rem++;
                    
}
                    
line.value = line.value.substring(rem);
                    
line.initLeading();
                
}
            
}
            
line = line.next;
        
}
    
}

    
/**
     
* Removes leading empty lines.
     
*
     
* @return <code>true</code> if an empty line was removed.
     
*/

    
public boolean removeLeadingEmptyLines()
    
{
        
boolean wasEmpty = false;
        
Line line = this.lines;
        
while (line != null && line.isEmpty)
        
{
            
this.removeLine(line);
            
line = this.lines;
            
wasEmpty = true;
        
}
        
return wasEmpty;
    
}

    
/**
     
* Removes trailing empty lines.
     
*/
    
public void removeTrailingEmptyLines()
    
{
        
Line line = this.lineTail;
        
while (line != null && line.isEmpty)
        
{
            
this.removeLine(line);
            
line = this.lineTail;
        
}
    
}

    
/**
     
* Splits this block's lines, creating a new child block having 'line' as
     
* it's lineTail.
     
*
     
* @param line
     
*
            
The line to split from.
     
* @return The newly created Block.
     
*/

    
public Block split(final Line line)
    
{
        
final Block block = new Block();

        
block.lines = this.lines;
        
block.lineTail = line;
        
this.lines = line.next;
        
line.next = null;
        
if (this.lines == null)
        
{
            
this.lineTail = null;
        
}
        
else
        
{
            
this.lines.previous = null;
        
}

        
if (this.blocks == null)
        
{
            
this.blocks = this.blockTail = block;
        
}
        
else
        
{
            
this.blockTail.next = block;
            
this.blockTail = block;
        
}

        
return block;
    
}

    
/**
     
* Removes the given line from this block.
     
*
     
* @param line
     
*
            
Line to remove.
     
*/

    
public void removeLine(final Line line)
    
{
        
if (line.previous == null)
        
{
            
this.lines = line.next;
        
}
        
else
        
{
            
line.previous.next = line.next;
        
}
        
if (line.next == null)
        
{
            
this.lineTail = line.previous;
        
}
        
else
        
{
            
line.next.previous = line.previous;
        
}
        
line.previous = line.next = null;
    
}

    
/**
     
* Appends the given line to this block.
     
*
     
* @param line
     
*
            
Line to append.
     
*/

    
public void appendLine(final Line line)
    
{
        
if (this.lineTail == null)
        
{
            
this.lines = this.lineTail = line;
        
}
        
else
        
{
            
this.lineTail.nextEmpty = line.isEmpty;
            
line.prevEmpty = this.lineTail.isEmpty;
            
line.previous = this.lineTail;
            
this.lineTail.next = line;
            
this.lineTail = line;
        
}
    
}

    
/**
     
* Changes all Blocks of type <code>NONE</code> to <code>PARAGRAPH</code> if
     
* this Block is a List and any of the ListItems contains a paragraph.
     
*/

    
public void expandListParagraphs()
    
{
        
if (this.type != BlockType.ORDERED_LIST && this.type != BlockType.UNORDERED_LIST)
        
{
            
return;
        
}
        
Block outer = this.blocks, inner;
        
boolean hasParagraph = false;
        
while (outer != null && !hasParagraph)
        
{
            
if (outer.type == BlockType.LIST_ITEM)
            
{
                
inner = outer.blocks;
                
while (inner != null && !hasParagraph)
                
{
                    
if (inner.type == BlockType.PARAGRAPH)
                    
{
                        
hasParagraph = true;
                    
}
                    
inner = inner.next;
                
}
            
}
            
outer = outer.next;
        
}
        
if (hasParagraph)
        
{
            
outer = this.blocks;
            
while (outer != null)
            
{
                
if (outer.type == BlockType.LIST_ITEM)
                
{
                    
inner = outer.blocks;
                    
while (inner != null)
                    
{
                        
if (inner.type == BlockType.NONE)
                        
{
                            
inner.type = BlockType.PARAGRAPH;
                        
}
                        
inner = inner.next;
                    
}
                
}
                
outer = outer.next;
            
}
        
}
    
}
}