<?php


namespace CMSMSExt\FS;


use CMSMSExt\Exceptions\Exception;
use CMSMSExt\Exceptions\RuntimeException;

class INI extends File
{
  protected $_buffer = [];
  
  
  protected function _load() : void
  {
    # this is deprecated in PHP 8.1, so we'll get rid of it soon
    if( \version_compare(\PHP_VERSION, '8.1.0') < 0 )
    {
      $old = \ini_get('auto_detect_line_endings');
      \ini_set('auto_detect_line_endings', TRUE);
      $this->_lines = \file($this->_fn, \FILE_IGNORE_NEW_LINES);
      \ini_set('auto_detect_line_endings', $old);
    }
    # -------------------------------------------------------------

    $section = '';
    
    foreach($this->_buffer as $line)
    {
      // comment or whitespace
      if( \preg_match('/^\s*(;.*)?$/', $line) || \preg_match('/^\s*(#.*)?$/', $line)  )
      {
        $this->_lines[] = ['type' => 'comment',
                           'data' => $line];
      }
      else if( \preg_match('/^\[(.*)]/', $line, $match) )
      {
        // section
        $section        = $match[1];
        $this->_lines[] = ['type' => 'section',
                           'data' => $line,
                           'section' => $section];
      }
      else if( \preg_match('/^\s*(.*?)\s*=\s*(.*?)\s*$/', $line, $match) )
      {
        if( \preg_match('/^\s*(.*?)\[(.*?)\]\s*=\s*(.*?)\s*$/', $line, $match) )
        {
          // array_entry
          $this->_lines[] = ['type'  => 'array_entry',
                             'data' => $line,
                             'section' => $section,
                             'key_array_name' => $match[1],
                             'key_array_key' => $match[2],
                             'key' => $match[1] . '['  . $match[2] . ']',
                             'value' => $match[3]
          ];
        }
        else if( \preg_match('/^\s*([^=]+)\s*=\s*(.*?)\s*$/', $line, $match) )
        {
          // entry
          $this->_lines[] = ['type'  => 'entry',
                             'data' => $line,
                             'section' => $section,
                             'key' => $match[1],
                             'value' => \trim($match[2], ' \n\r\t\v\x00"\'')
          ];
        }
      }
    }
    
    $this->_buffer = [];
  }
  
  /**
   * @param $section
   * @param $key
   * @param $value
   */
  protected function _set($section, $key, $value)
  {
    foreach($this->_lines as &$line)
    {
      if('entry' !== $line['type']) { continue; }
    
      if($line['section'] !== $section) { continue; }
    
      if($line['key'] !== $key) { continue; }
    
      $line['value'] = $value;
      $line['data']  = $key . ' = ' . $value;
    }
  }
  
  protected function _set_array($section, $key, $value)
  {
    if( $this->_isAssoc($value) )
    {

      foreach($key as $k => $v)
      {
        $flat_key = $key . '[' . $k . ']';
        $this->_set($section, $flat_key, $v);
      }
    }
    else
    {
      foreach($key as $v)
      {
        $flat_key = $key . '[]';
        $this->_set($section, $flat_key, $v);
      }
    }
  }
  
  protected function _section_exists(string $section = '')
  {
    if(empty($section) ) { return FALSE; }
    
    foreach($this->_lines as $line)
    {
      if($line['section'] !== $section) { continue; }
 
      return TRUE;
    }
    
    return FALSE;
  }
  
  protected function _key_exists(string $section = '', $key = '')
  {
    
    foreach($this->_lines as $line)
    {
      if($line['section'] !== $section) { continue; }
      if($line['key'] !== $key && $line['key_array_name'] !== $key){ continue; }
  
      return  TRUE;
    }
    
    return FALSE;
  }
  
  protected function _get_section_end_pointer($section = '')
  {
    $found = FALSE;
    $p = -1;
    $l = 0;
    foreach( $this->_lines as $line)
    {
      if(!$found) {$p++;}
      if( $section !== $line['section'] ) { continue; }
      $found = TRUE;
      $l++;
    }
    
    return $found ? $p + $l : -1;
  }
  
  protected function _insert_split_array( $section, $key, $value)
  {
    $p = $this->_get_section_end_pointer($section);
    $lines = [];
    if( \is_array($key) )
    {
      if( $this->_isAssoc($value) )
      {
        foreach($key as $k => $v)
        {
          $flat_key = $key . '[' . $k . ']';
          $line['value'] = $v;
          $line['data']  = $flat_key . ' = ' . $v;
          $lines[] = $line;
        }
      }
      else
      {
        foreach($key as $v)
        {
          $flat_key = $key . '[]';
          $line['value'] = $v;
          $line['data']  = $flat_key . ' = ' . $v;
          $lines[] = $line;
        }
      }
    }
    else
    {
      $lines[] = ['type'  => 'entry',
       'data' => $key . ' = ' . $value,
       'section' => $section,
       'key' => $key,
       'value' => $value];
    }
    
    
    \array_splice( $this->_lines, $p, 0, $lines );
  }
  
  protected function _insert($section, $key, $value)
  {
    $section = \trim($section);
    
    if( !empty($section) && !$this->_section_exists($section) )
    {
      if( \is_array($key) )
      {
        $this->_insert_array($section, $key, $value);
      }
      else
      {
        $this->_lines[] = ['type' => 'section',
                           'data' => '[' . $section . ']',
                           'section' => $section];
  
        $this->_lines[] = ['type'  => 'entry',
                           'data' => $key . ' = ' . $value,
                           'section' => $section,
                           'key' => $key,
                           'value' => $value];
      }

    }
    else
    {
      $this->_insert_split_array($section, $key, $value);
    }
  
  }
  
  protected function _insert_array($section, $key, $value)
  {
    if( $this->_isAssoc($value) )
    {
      foreach($value as $k => $v)
      {
        $flat_key = $key . '[' . $k . ']';
        $this->_insert($section, $flat_key, $v);
      }
    }
    else
    {
      foreach($value as $v)
      {
        $flat_key = $key . '[]';
        $this->_insert($section, $flat_key, $v);
      }
    }
  }
  
  /**
   * @param array $arr
   *
   * @return bool
   */
  protected function _isAssoc(array $arr)
  {
    if([] === $arr) { return FALSE; }
    
    return \array_keys($arr) !== \range(0, \count($arr) - 1);
  }
  
  public function get($section, $key)
  {
    $array_entry = [];
    
    foreach($this->_lines as $line)
    {
      if('entry' !== $line['type'] && 'array_entry' !== $line['type']) { continue; }
      
      if($line['section'] !== $section) { continue; }
      
      if($line['key'] !== $key && $line['key_array_name'] !== $key) { continue; }
      
      if('entry' === $line['type']) { return $line['value']; }
      
      if('array_entry' === $line['type'])
      {
        if( !empty($line['key_array_key']) )
        {
          $array_entry[$line['key_array_key']] = $line['value'];
        }
        else
        {
          $array_entry[] = $line['value'];
        }
      }
    }
    
    if( !empty($array_entry) )
    {
      return $array_entry;
    }
    
    return '';
  }
  
  public function set($section, $key, $value)
  {
    if( $this->_key_exists($section, $key) )
    {
      if( \is_array($value) )
      {
        $this->_set_array($section, $key, $value);
      }
      else
      {
        $this->_set($section, $key, $value);
      }
    }

    # doesn't exist
    if( \is_array($value) )
    {
      $this->_insert_array($section, $key, $value);
    }
    else
    {
      $this->_insert($section, $key, $value);
    }
    
    
    return $this;
  }
  
  public function read() : string
  {
    $tmp = [];
    
    foreach($this->_lines as $line) { $tmp[] = $line['data']; }
    
    return \implode(PHP_EOL, $tmp);
  }
  
  /**
   * @throws \CMSMSExt\Exceptions\RuntimeException
   */
  public function fromArray(array $array = [])
  {
    throw new RuntimeException('Invalid method call: ' . __METHOD__);
  }
  
  public function save(string $file_name = '') : File
  {
    
    $fn = $this->_fn;
    
    if( !empty($file_name) )
    {
      $fn = $this->_conform_fn($fn);
    }
    
    file_put_contents($fn, $this->read() );
    
    return $this;
  }
  
}
?>