#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2014 Stefan Müller-Klieser, PHYTEC Messtechnik GmbH
import argparse, os, subprocess, shutil

# Global constants
phylinux_version = "PD14.1-rc1"
phylinux_storepath = "phy2octo/phylinux"
repo_install_path = "/usr/local/bin"
repo_url = "https://storage.googleapis.com/git-repo-downloads/repo"
phytec_repo_url = "git://git.phytec.de/phy2octo"
phytec_repo_internal = "ssh://git@git.phytec.de/phy2octo-dev"
phytec_bsp_basedir = "sources/meta-phytec/meta-phy"
repo_reference_dir = "/home/share/repo_reference"

# Global vars
yocto_version = ""
selected_soc = ""
selected_release = ""
selected_machine = ""

##################
# generic helper #
##################
def which(program):
    def is_exe(fpath):
        return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

    fpath, fname = os.path.split(program)
    if fpath:
        if is_exe(program):
            return program
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            path = path.strip('"')
            exe_file = os.path.join(path, program)
            if is_exe(exe_file):
                return exe_file
    return None

def is_in_path(question):
    for path in os.environ["PATH"].split(os.pathsep):
        path = path.strip('"')
        if path == question:
            return True
    return False

def call(cmd):
    print cmd
    subprocess.call(cmd,shell=True)
    print ''

def remove_file_extension(f):
    name = f.rsplit('.',1)
    return name[0]

###################
# phylinux helper #
###################

def sanity_fail():
    print ''
    print 'Sanity check failed!'
    print 'You are most probably doing something wrong. phyLinux should be called in an empty directory'
    print 'or in an directory were there is an PHYTEC BSP installed already. If you know what you are doing,'
    print 'you could:'
    print '    $ touch .insane'
    print 'to circumvent the sanity checker'
    print ''
    raise SystemExit

def sanity_check():
    if os.path.isfile('.insane'):
        return None
    if not which('wget'):
        print 'need wget to download the packages'
        raise SystemExit
    cwd = os.getcwd()
    if phylinux_storepath in cwd:
        print 'executing from within the storage directory'
        sanity_fail()
    if os.path.exists('.git'):
        print 'executing from within git repository'
        sanity_fail()

def install_repo_wrapper():
    if not probe_repo_wrapper():
        print 'installing repo tool wrapper...'
        if is_in_path(repo_install_path):
            print 'installing in '+ repo_install_path
            target =  os.path.join(repo_install_path, 'repo')
            call('wget ' + repo_url)
            call('chmod a+x repo')
            call('sudo mv repo '+ target)
            return True
        else:
            print repo_install_path + ' is not in $PATH. Add it to the path or modify'
            print 'the installer location'
            return False
    return True

def probe_repo_wrapper():
    repo_exec = which('repo')
    if repo_exec:
        print 'repo tool wrapper is installed: ' + repo_exec
        return True
    else:
        print 'repo tool wrapper is not installed'
        return False

def probe_repo_currentdir():
    if os.path.exists('.repo/repo/main.py'):
        print 'repo is installed in the current directory'
        return True
    else:
        print 'no repo repository in current directory'
        return False

def probe_yocto_version():
    global selected_soc, selected_release, yocto_version
    if selected_soc == "" or selected_release == "":
        print 'cannot probe for yocto version if we did not choose a BSP release'
        return False
    if not os.path.exists('sources/poky/meta-yocto'):
        print 'cannot find yoctos poky distribution in sources/poky'
        return False
    f = open('sources/poky/meta-yocto/conf/distro/poky.conf','r')
    fl = f.readlines()
    f.close()
    for line in fl:
        if "DISTRO_VERSION" in line:
            version = line.split('=',1)[1].strip()
            version = version[1:-1]
            if 'snapshot' in version:
                version = version.split('+',1)[0] + '-snapshot'
            print 'probing Yocto Version to ' + version
            yocto_version = version
            return True
    print 'no Yocto version found in poky distribution'
    yocto_version = ""
    return False

def probe_selected_soc(arg_soc = None):
    global selected_soc
    # first see if arg_soc is a valid input
    if arg_soc:
        if probe_gitbranch(arg_soc):
            print 'Selecting SoC to command line argument: '+arg_soc
            selected_soc = arg_soc
            return True
        else:
            print 'Command line argument '+arg_soc+' is no valid SoC'
            return False
    # find out if there has been a selection before
    try:
        f = open('.repo/manifests.git/config', 'r')
        fl = f.readlines()
        f.close()
        for line in fl:
            if "merge" in line:
                soc = line.split('=')[1].strip()
                if not soc.endswith('master'):
                    selected_soc = soc
                    print 'probing selected soc to ', soc
                    return True
    except Exception as e:
       print 'Error while probing for SoC'
    print 'No SoC Platform selected'
    return False

def probe_selected_release():
    global selected_release
    try:
        f = os.readlink('.repo/manifest.xml')
        release = f.split('/',1)[1]
        if release[0:2] == 'PD' and release.endswith('.xml'):
            selected_release = remove_file_extension(release)
            print 'Release: ' + selected_release + ' is currently selected'
            return True
        else:
            print release + ' is no propper release'
    except Exception as e:
        print 'Error while probing for selected Release'
        print type(e),e.args,e
    print 'No Release selected'
    return False

def probe_gitbranch(branch):
    return os.path.exists('.repo/manifests.git/logs/refs/remotes/origin/'+branch)

def probe_selected_machine():
    global selected_soc, selected_release, yocto_version, selected_machine
    print 'probing for selected machine'
    if selected_soc == "" or selected_release == "" or yocto_version == "":
        print 'cannot probe for machine if we did not set up a BSP correctly'
        return False
    lconf = os.path.join('build/conf/local.conf')
    if not os.path.exists(lconf):
        print 'No local.conf! Yocto BSP not correctly configured!'
        return False
    f = open(lconf,'r')
    fl = f.readlines()
    f.close()
    for line in fl:
        if "MACHINE" in line and not line.strip().startswith("#"):
            print 'Machine set in local conf is: ', line
            selected_machine = line.split('"')[1]
            return True
    print 'no MACHINE selection found'
    return False

def choose_soc():
    print '***************************************************'
    print '* Please choose one of the available SoC Platforms:'
    print '*'
    branches = os.listdir('.repo/manifests.git/logs/refs/remotes/origin')
    branches.sort()
    branches.remove("master")
    for index, branch  in enumerate(branches):
        print '*   '+str(index+1)+': '+branch
    print '*'
    while True:
        try:
            user_input = int(raw_input('$ '))-1
            if user_input < 0 or user_input >= len(branches):
                raise ValueError
            break
        except ValueError:
            print 'No valid input.  Try again...'
    return branches[user_input]

def choose_release():
    print '***************************************************'
    print '* Please choose one of the available Releases:'
    print '*'
    d = os.listdir('.repo/manifests')
    d.sort()
    releases = []
    for i in d:
        if "PD" in i:
            releases.append(i)
    for index, release  in enumerate(releases):
        print '*   '+str(index+1)+': '+remove_file_extension(release)
    print '*'
    while True:
        try:
            user_input = int(raw_input('$ '))-1
            if user_input < 0 or user_input >= len(releases):
                raise ValueError
            break
        except ValueError:
            print 'No valid input.  Try again...'
    return remove_file_extension(releases[user_input])

def choose_machine():
    print '***************************************************'
    print '* Please choose one of the available Machines:'
    print '*'
    d = os.listdir(phytec_bsp_basedir + selected_soc +
            '/conf/machine/')
    d.sort()
    machines = []
    for i in d:
        if '.conf' in i:
            machines.append(remove_file_extension(i))
    for index, machine  in enumerate(machines):
        # print @DESCRIPTION tag from machine.conf
        desc = "no description found"
        file = open(phytec_bsp_basedir + selected_soc + '/conf/machine/' + machine + '.conf')
        for line in file.readlines():
            if '@DESCRIPTION' in line:
                desc = line.split(":",1)[1][:-1]
                break
        print '*   '+str(index+1)+': '+ machine + ": ", desc
    print '*'
    while True:
        try:
            user_input = int(raw_input('$ '))-1
            if user_input < 0 or user_input >= len(machines):
                raise ValueError
            break
        except ValueError:
            print 'No valid input.  Try again...'
    return machines[user_input]

def set_configuration_in_localconf():
    global yocto_version, selected_machine, selected_release
    lconf = 'build/conf/local.conf'
    lconfbak = 'build/conf/local.conf.bak'
    if not os.path.exists(lconf):
        print 'You dont have a local.conf'
        return False
    print 'creating a backup of you old local.conf to local.conf.bak'
    shutil.copyfile(lconf,lconfbak)
    fw = open(lconf,'w')
    fr = open(lconfbak,'r')
    frl = fr.readlines()
    set_already = False
    for line in frl:
        if "MACHINE" in line and not line.strip().startswith("#") and not set_already:
            print 'set MACHINE in local.conf to ', selected_machine
            fw.write('MACHINE ?= "' + selected_machine + '"\n')
            set_already = True
        elif "BSP_VERSION" in line and not line.strip().startswith("#"):
            print 'set BSP_VERSION in local.conf to ', selected_release
            fw.write('BSP_VERSION = "' + selected_release + '"\n')
        else:
            fw.write(line)
    fr.close()
    fw.close()
    if not set_already:
        print 'Could not set MACHINE in local.conf'
        return False
    return True

def rc_warning():
    print '     {}  _---_  {}'
    print '       \/     \/  '
    print '        |() ()|   '
    print '         \ + /    '
    print '        / HHH  \  '
    print '      {}  \_/   {}'
    print ''
    print 'WARNING!! You are working on a release candidate version of Phytecs'
    print 'Linux BSP, aka ALPHA Version. You will have to port all you work'
    print 'to a Final Release version later on. There will be no backports for'
    print 'this version.'
    print ''

################################################
##
## COMMANDS
##
################################################
def cmd_init(args):
    global selected_soc, selected_release, selected_machine, yocto_version
    if not install_repo_wrapper():
        print 'no repo wrapper'
        return False

    if not probe_repo_currentdir:
        print 'installing repo tool'

    print 'updating remote phytec repo repository'
    if args.dev:
        call('repo init -u ' + phytec_repo_internal + ' --reference=' + repo_reference_dir)
    else:
        call('repo init -u ' + phytec_repo_url)

    if args.platform and probe_gitbranch(args.platform):
        print 'Selecting SoC to command line argument: ' + args.platform
        selected_soc = args.platform
    else:
        probe_selected_soc()
        selected_soc = choose_soc()
    print 'switching to ' + selected_soc
    call('repo init -b ' + selected_soc)

    if args.release and os.path.exists('.repo/manifests/' + args.release + '.xml'):
        print 'Selecting Release to command line argument: ' + args.release
        selected_release = args.release
    else:
        probe_selected_release()
        selected_release = choose_release()
    print 'switching to ' + selected_release
    call('repo init -m ' + selected_release + '.xml')

    print 'syncing sources'
    if args.dev:
        if not os.path.exists('.repo/local_manifests'):
            os.mkdir('.repo/local_manifests')
        if os.listdir('.repo/local_manifests'):
            print '**'
            print '** WARNING! You have files in local_manifests folder. Double Check!'
            print '** You may need to copy the local manifest files yourself'
            print '**'
        else:
            print 'copy dev manifest to local_manifests'
            devmanifest = os.path.join('.repo/manifests/dev', selected_release+'.xml')
            if os.path.exists(devmanifest):
                shutil.copyfile(devmanifest, '.repo/local_manifests/'+selected_release+'.xml')
            else:
                print 'Warning! No dev manifest found for release: ',  selected_release
    call('repo sync -f')

    if not os.path.exists('sources/templateconf'):
        print 'No templateconf path found in sources! Please use a correct PD Release'
        return False
    #TODO: This bugfix here should be repaired in the repo manifest.xml
    # file permissions from the templateconf is wrong we need to fix it here
    #TODO: the copyfile instruction does not sync files, this can lead to 
    # unexpected directory states
    for f in os.listdir('sources/templateconf'):
        os.chmod('sources/templateconf/'+f, 0644)

    print 'Setting environment and starting the poky init script'
    call('bash -c "TEMPLATECONF=\"../sources/templateconf\" source sources/poky/oe-init-build-env" > /dev/null')

    if args.machine:
        selected_machine = args.machine
    else:
        selected_machine = choose_machine()
    set_configuration_in_localconf()

    print ''
    print ''
    print 'to start working with Yocto you have to type:'
    print '    $ source sources/poky/oe-init-build-env'
    print ''
    print ''
    if 'rc' in selected_release:
        rc_warning()

# info command
def cmd_info(args):
    global selected_soc, selected_release, selected_machine, yocto_version
    probe_selected_soc()
    probe_selected_release()
    probe_yocto_version()
    probe_selected_machine()
    print '**********************************************'
    print '* The current BSP configuration is:  '
    print '*'
    print '* SoC: ', selected_soc
    print '* Release: ', selected_release
    print '* Machine: ', selected_machine
    print '* Yocto Version: ', yocto_version
    print '*'
    print '**********************************************'
    if 'rc' in selected_release:
        rc_warning()

# release command
def cmd_release(args):
    if not args.name:
        print 'Setname for release:'
        user_input = raw_input('$ ')
        args.name = user_input
    call('repo manifest -o '+args.name+'.xml -r')

##############
# Executable #
##############

def main():
    global selected_soc, selected_release, yocto_version
    sanity_check()

    # parse commands
    # global options
    global_parser = argparse.ArgumentParser(add_help=False)
    global_parser.add_argument('--verbose', dest='v', action='store_true')
    global_parser.add_argument('--dev', action='store_true',help=argparse.SUPPRESS)

    parser = argparse.ArgumentParser(description='This Programs sets up an environment to work with The Yocto Project'
                                                 ' on Phytecs Development Kits',
                                     version=phylinux_version,
                                     parents=[global_parser])
    subparsers = parser.add_subparsers(help='commands',dest='command')

    # init command
    init_parser = subparsers.add_parser('init', parents=[global_parser],
                    help='init the phytec bsp in the current directory')
    init_parser.add_argument('-p', dest='platform', help='Processor platform')
    init_parser.add_argument('-r', dest='release', help='Release version')
    init_parser.add_argument('-m', dest='machine', help='Machine')
    
    # info command
    info_parser = subparsers.add_parser('info', parents=[global_parser], 
                    help='print info about the phytec bsp in the current directory')

    # release command
    release_parser = subparsers.add_parser('release', parents=[global_parser],
                        help='create an relase snapshot from current workingdir')
    release_parser.add_argument('-n', dest='name', default="release", help='release name')

    args = parser.parse_args()
    if args.command == 'init':
        cmd_init(args)
    elif args.command == 'info':
        cmd_info(args)
    elif args.command == 'release':
        cmd_release(args)
    elif args.command == '':
        cmd_()

if __name__ == "__main__":
  main()
