<?php
/**
 * FIDD class. See ../README.md.
 *
 * PHP 5.3
 *
 * @license http://www.opensource.org/licenses/MIT MIT
 * @author Romain d'Alverny @rdalverny
*/

include 'lib/lib.php';
include 'lib/fabpot-yaml/lib/sfYaml.php';
include 'lib/magnet.php';

/**
*/
class FIDD
{
    /**
     * @param string $app_path
     * @param string $url rsync url to sync from
     * @param string $prefix
     *
     * @return boolean
    */
    public static function run($app_path, $url, $prefix, $netinstall = false, $netinstall_rename = '')
    {
        $tmp_path = $app_path . '/var/tmp';
        
        echo sprintf("rsync'ing bcd.mageia.org files from %s...\n", $url);
        if (self::rsync_mirror_files($url, $tmp_path, $netinstall)) {
            echo "Failed to rsync remote ISOs...\n";
            return false;
        }
        echo "fetching ISOs filesize...\n";
        $filesizes = self::rsync_get_filesizes($url, $netinstall);
        echo "building definition file...\n";
        if (!$netinstall) {
            $info = self::build_definitions($prefix, $tmp_path, $filesizes);
        } else {
            $info = self::build_definitions_net_install($prefix, $tmp_path, $filesizes, $netinstall_rename);
        }
        self::save_definitions($info, $app_path . '/var/definitions');
        
        return true;
    }

    /**
     * Dump $definitions into JSON, YAML and INI formats in $dir.
     *
     * @param array $definitions
     * @param string $app_root
     *
     * @return void
    */
    private static function save_definitions($definitions, $dir)
    {
        ksort($definitions);

        // JSON
        file_put_contents($dir . '/data.json', json_encode($definitions));

        // dump into YAML
        $s = sfYaml::dump($definitions, 10);
        file_put_contents($dir . '/data.yaml', $s);

        // dump into INI
        write_ini_file($definitions, $dir . '/data.ini', true);

        echo sprintf("Found %d items:\n - %s\n\nand %d invalid ones.\n",
            count($definitions),
            implode("\n - ", array_keys($definitions)),
            isset($definitions['invalid']) ? count($definitions['invalid']) : 0);

        echo "Data is now available in 3 formats: var/definitions/data.{json,yaml,ini}. Help yourself!\n\n";
    }

    /**
     * Mirror files from remote.
     *
     * @param string $url
     * @param string $dir where to mirror files
    */
    private static function rsync_mirror_files($url, $dir, $netinstall = false)
    {
        if (!$netinstall){
        $cmd = sprintf('/usr/bin/rsync -avHP --exclude=*.iso %s %s',
            escapeshellarg($url),
            escapeshellarg($dir));
        } else {
            $cmd = sprintf('/usr/bin/rsync -avHP --exclude=*.iso --exclude=*.img --include=i586 --include=x86_64 --include=*/install --include=*/install/images --include=*/install/images/* --exclude=* %s %s',
            escapeshellarg($url),
            escapeshellarg($dir));
        }
        exec($cmd, $out, $ret);
        return $ret;
    }

    /**
     * Get and format all ISOs file size.
     *
     * @param string $url rsync url where to look at data.
     *
     * @return array ["file.iso" => "size", ...]
    */
    private static function rsync_get_filesizes($url, $netinstall = false)
    {
        if (!$netinstall){
            $cmd = sprintf('/usr/bin/rsync -a --list-only --no-h %s | awk \'{print $2"\t"$5}\'',
            escapeshellarg($url));
        }else{
            $cmd = sprintf('/usr/bin/rsync -a --list-only --include=i586 --include=x86_64 --include=*/install --include=*/install/images --include=*/install/images/* --exclude=* --no-h %s | awk \'{print $2"\t"$5}\'',
            escapeshellarg($url));
        }
        $isos = array();
        exec($cmd, $out, $ret);
        if ($ret === 0) {
            foreach ($out as $line) {
                $line = explode("\t", trim($line));
                if (count($line) > 1) {
                    if (trim(substr($line[1], -3, 3)) == 'iso') {
                        $file = explode('/', $line[1]);
                        if (!$netinstall) {
                            $isos[$file[1]] = formatBytes($line[0], 1);
                        } else {
                            $isos[$file[3]] = formatBytes($line[0], 1);
                        }
                    }
                }
            }
        }
        return $isos;
    }

    /**
     * Parse mirrors files in $dir and build a catalog of these.
     *
     * @param string $prefix
     * @param string $dir where to look for files
     * @param array $filesizes sizes of each .iso file
     *
     * @return array
    */
    function build_definitions($prefix, $dir, $filesizes)
    {
        $info = array();
        $isos = glob($dir . '/*');
        
        foreach ($isos as $isodir) {
            $data  = array();
            $files = glob($isodir . '/*.*');
            foreach ($files as $f) {
                $fi  = pathinfo($f);
                $ext = $fi['extension'];
                switch ($fi['extension'])
                {
                    case 'md5':
                        $key          = str_replace('.iso.md5', '.iso', basename($f));
                        $data['file'] = $key;
                        $data['md5']  = self::get_checksum_value($f);
                        $md5gpg          = str_replace('.iso.md5', '.md5.gpg', basename($f));
                        $data['md5.gpg'] = $md5gpg;
                    break;
                    case 'sha3':
                        $data['sha3'] = self::get_checksum_value($f);
                        $sha1gpg          = str_replace('.iso.sha1', '.sha1.gpg', basename($f));
                        $data['sha3.gpg'] = $sha1gpg;
			break;
                    case 'sha512':
                        $data['sha512'] = self::get_checksum_value($f);
                        $sha512gpg          = str_replace('.iso.sha512', '.sha512.gpg', basename($f));
                        $data['sha512.gpg'] = $sha512gpg;
                    break;
                    case 'langs': $data['langs'] = implode(', ', explode("\n", trim(file_get_contents($f))));
                    break;

                }
            }
            if ( count($data) == 0) {
                // skip the first one
            } else if (null !== ($ret = self::is_correct_name($data['file']))) {
                $data['path']    = sprintf('%s/%s',
                    $ret['release'] == '' ? sprintf('iso/%s', $ret['version']) : sprintf('iso/cauldron%s', $prefix),
                    str_replace('.iso', '', $data['file'])
                );
                $torrentfile = str_replace('.iso', '.torrent', $data['file']);
                $data['torrent']    = sprintf('%s/%s',
                    $ret['release'] == '' ? sprintf('iso/%s/torrents', $ret['version']) : sprintf('iso/cauldron%s/torrents', $prefix),
                    $torrentfile
                );
                $torrentfile = "var/tmp/torrents/".$torrentfile;
                $data['magnet']    = sprintf('%s&dn=%s&tr=%s',
                    magnet($torrentfile),
                    str_replace('.iso', '', $data['file']),
                    'udp%3A%2F%2Ftracker.torrent.eu.org%3A451&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337');
                $data['name']    = str_replace(array('-', '.iso', 'i586', 'x86_64'), array(' ', '', '32bit', '64bit'), $data['file']);
                $data['size']    = $filesizes[$data['file']];

                $info[$data['file']] = $data;
            } else {
                $info['invalid'][] = $data;
            }
            //$info[str_replace($prefix . '-', '', $key)] = $data;
        }

        return $info;
    }

    /**
     * Parse mirrors files in $dir and build a catalog of these.
     *
     * @param string $prefix
     * @param string $dir where to look for files
     * @param array $filesizes sizes of each .iso file
     *
     * @return array
    */
    function build_definitions_net_install($prefix, $dir, $filesizes, $netinstall_rename)
    {
        $info = array();
        $isos = glob($dir . '/*/install/images/');
        
        foreach ($isos as $isodir) {
            $data  = array();
            $files = glob($isodir . '/*.iso*');
            foreach ($files as $f){
                $fi  = pathinfo($f);
                $ext = $fi['extension'];
                switch ($fi['extension'])
                {
                    case 'md5':
                        $key          = str_replace('.iso.md5', '.iso', basename($f));
                        $data['file'] = $key;
                        $data['md5']  = self::get_checksum_value($f);
                    break;
                    case 'sha3':
                        $data['sha3'] = self::get_checksum_value($f);
                    break;
                    case 'sha512':
                        $data['sha512'] = self::get_checksum_value($f);
                    break;
                }
            if ( count($data) == 0) {
                // skip the first one
            } else if (null !== ($ret = self::is_correct_name($data['file']))) {
		    if ($ret['version'] == 'Cauldron')
		    {
			$rename    = str_replace('Cauldron', $netinstall_rename, $data['file']);
	                $data['path']    = sprintf('%s/%s/install/images',
        	            sprintf('distrib/cauldron%s', $prefix),
			    $ret['arch']
			);
                        $data['name']    = str_replace(array('-', '.iso', 'i586', 'x86_64'), array(' ', '', '32bit', '64bit'), $rename);
               	        $data['size']    = $filesizes[$data['file']];
                        $info[$rename] = $data;
		    } else {
                        $data['path']    = sprintf('%s/%s/install/images',
                           $ret['release'] == '' ? sprintf('distrib/%s', $ret['version']) : sprintf('distrib/cauldron%s', $prefix),
                           $ret['arch']
                        );
                        $data['name']    = str_replace(array('-', '.iso', 'i586', 'x86_64'), array(' ', '', '32bit', '64bit'), $data['file']);
                        $data['size']    = $filesizes[$data['file']];
		        $info[$data['file']] = $data;
		    }
            } else {
                $info['invalid'][] = $data;
            }
            }
        }

        return $info;
    }

    /**
     * Extract checksum from .md5 or .sha1 file.
     *
     * @param string $f
     *
     * @return string
    */
    private static function get_checksum_value($f)
    {
        $s = trim(file_get_contents($f));
        $s = explode("  ", $s);
        return trim($s[0]);
    }

    /**
     * Validate and extract Mageia image name.
     *
     * @see https://wiki.mageia.org/en/Product_naming
     * 
     * @param string $s
     *
     * @return array
    */
    public static function is_correct_name($s)
    {
        $re = '/^(Mageia)-(\d+(.\d)?|Cauldron)(-(alpha|beta|RC|rc)(\d*))?(-(.*))?-(i586|x86_64|dual)?(-(CD|DVD|BR))?(-(build\_\w+))?\.(.*)$/';
        if (preg_match($re, $s, $arr)) {
            $ret = array(
                'name'    => $arr[1],
                'version' => $arr[2],
                'release' => $arr[4] . $arr[5],
                'variant' => $arr[7],
                'arch'    => $arr[9],
                'medium'  => $arr[10],
                'build'   => $arr[12],
                'ext'     => $arr[13]
            );
            return $ret;
        }
        return false;
    }
}