#!/usr/bin/env python3

import os
import shutil
import sys
import platform
import subprocess
import tempfile

prefix = '/usr/local'
clean = True
defaultimpl = 'kernel'

def hostsanitize(host):
  host = host.split('-')[0]
  host = ''.join(c for c in host if c in '_0123456789abcdefghijklmnopqrstuvwxyz')
  for prefix,result in (
    ('amd64','amd64'), ('x86_64','amd64'),
    ('x86','x86'), ('i386','x86'), ('i686','x86'),
    ('arm64','arm64'), ('armv8','arm64'), ('aarch64','arm64'),
    ('arm','arm32'),
    ('riscv64','riscv64'),
    ('riscv','riscv32'),
    ('mips64','mips64'),
    ('mips','mips32'),
    ('ppc64','ppc64'), ('powerpc64','ppc64'),
    ('powerpc','ppc32'),
    ('ppc','ppc32'),
    ('sparc64','sparc64'), ('sparcv9','sparc64'), ('sun4u','sparc64'),
    ('sparc','sparc32'), ('sun','sparc32'),
  ):
    if host.startswith(prefix): return result
  return host

host = hostsanitize(platform.machine())

configurelog = ''

def log(x):
  global configurelog
  configurelog += x+'\n'
  print(x)

makefile = ''

for arg in sys.argv[1:]:
  if arg.startswith('--prefix='):
    prefix = arg[9:]
    continue
  if arg.startswith('--host='):
    host = hostsanitize(arg[7:])
    continue
  if arg == '--clean':
    clean = True
    continue
  if arg == '--noclean':
    clean = False
    continue
  raise ValueError('unrecognized argument %s' % arg)

echoargs = './configure'
echoargs += ' --prefix=%s' % prefix
echoargs += ' --host=%s' % host
if clean: echoargs += ' --clean'
if not clean: echoargs += ' --noclean'
log(echoargs)

if prefix[0] != '/':
  raise ValueError('prefix %s is not an absolute path' % prefix)

if clean:
  shutil.rmtree('build/%s' % host,ignore_errors=True)

def dirlinksym(dir,source,target):
  with tempfile.TemporaryDirectory(dir=dir) as t:
    os.symlink(target,'%s/symlink' % t)
    os.rename('%s/symlink' % t,'%s/%s' % (dir,source))

os.makedirs('build/%s' % host,exist_ok=True)
os.makedirs('build/%s/package/bin' % host,exist_ok=True)
os.makedirs('build/%s/package/lib' % host,exist_ok=True)
os.makedirs('build/%s/package/include' % host,exist_ok=True)

if clean:
  os.symlink('../..','build/%s/src' % host)

def copytree(src,dst,acceptfn=None):
  # starting with python 3.8 can use shutil.copytree
  # with dirs_exist_ok=True
  # but want to support older versions of python too
  # and want to symlink rather than copying

  os.makedirs(dst,exist_ok=True)
  for fn in sorted(os.listdir(src)):
    srcfn = '%s/%s' % (src,fn)
    if os.path.isdir(srcfn):
      dstfn = '%s/%s' % (dst,fn)
      copytree(srcfn,dstfn)
    else:
      if acceptfn is not None:
        if not acceptfn(fn): continue
      dirlinksym(dst,fn,'../'*(len(dst.split('/'))-2)+'src/'+srcfn)
  shutil.copystat(src,dst)

copytree('scripts-build','build/%s/scripts' % host)

with open('project/library') as f:
  projectlibrary = f.read().strip()

# ----- compilers

def compilerversion(c):
  try:
    p = subprocess.Popen(c.split()+['--version'],stdout=subprocess.PIPE,stderr=subprocess.STDOUT,universal_newlines=True)
    out,err = p.communicate()
    assert not err
    assert not p.returncode
    return out
  except:
    pass

firstcompiler = None

with open('compilers/default') as f:
  for c in f.readlines():
    c = c.strip()
    cv = compilerversion(c)
    if cv == None:
      log('skipping default compiler %s' % c)
      continue
    log('using default compiler %s' % c)
    firstcompiler = c
    break

if firstcompiler is None:
  raise ValueError('did not find a working compiler')

with open('build/%s/scripts/cdcompile' % host,'w') as f:
  f.write('#!/bin/sh\n')
  f.write('\n')
  f.write('cd "$1"; shift\n')
  f.write('exec %s "$@"\n' % firstcompiler)
os.chmod('build/%s/scripts/cdcompile' % host,0o755)

# ----- projectlibrary

def alternatives(dir,fn):
  with open('%s/%s' % (dir,fn)) as f:
    for line in f:
      line = line.split()
      if len(line) >= 3 and line[:2] == ['//','automatic-alternatives']:
        return ' '.join(line[2:])
  return '1'

objects = {}

for dir in 'kernel','openssl','include':
  builddir = 'build/%s/%s' % (host,dir)
  os.makedirs(builddir,exist_ok=True)
  for fn in sorted(os.listdir(dir)):
    dirlinksym(builddir,fn,'../src/%s/%s' % (dir,fn))

    if dir == 'include':
      shutil.copy2('include/%s'%fn,'build/%s/package/include/%s'%(host,fn))

    if fn.endswith('.c'):
      impl = alternatives(dir,fn)

      base = fn[:-2]
      M = '%s/%s.o: %s/%s.c' % (dir,base,dir,base)
      for obj in impl.split()[1:]:
        if obj.endswith('.o') and not obj.startswith('-l'):
          M += ' %s/%s' % (dir,obj)
      M += '\n'
      M += '\tscripts/compilealternatives %s %s %s\n' % (dir,base,alternatives(dir,fn))
      M += '\n'
      makefile = M + makefile

      if dir not in objects:
        objects[dir] = []
      objects[dir] += ['%s/%s.o'%(dir,base)]

for impl in sorted(objects):
  M = 'package/lib/lib%s-%s.a: scripts/staticlib %s\n' % (projectlibrary,impl,' '.join(objects[impl]))
  M += '\tscripts/staticlib lib%s-%s %s\n' % (projectlibrary,impl,' '.join(objects[impl]))
  M += '\n'
  makefile = M + makefile

  M = 'package/lib/lib%s-%s.so.1: scripts/sharedlib %s\n' % (projectlibrary,impl,' '.join(objects[impl]))
  if impl == 'openssl':
    M += '\tscripts/sharedlib lib%s-%s %s -lcrypto\n' % (projectlibrary,impl,' '.join(objects[impl]))
  else:
    M += '\tscripts/sharedlib lib%s-%s %s\n' % (projectlibrary,impl,' '.join(objects[impl]))
  M += '\n'
  makefile = M + makefile

  M = 'package/lib/lib%s-%s.so: package/lib/lib%s-%s.so.1\n' % (projectlibrary,impl,projectlibrary,impl)
  M += '\trm -f package/lib/lib%s-%s.so\n' % (projectlibrary,impl)
  M += '\tln -s lib%s-%s.so.1 package/lib/lib%s-%s.so\n' % (projectlibrary,impl,projectlibrary,impl)
  M += '\n'
  makefile = M + makefile

for link in 'a','so.1','so':
  M = 'package/lib/lib%s.%s: package/lib/lib%s-%s.%s\n' % (projectlibrary,link,projectlibrary,defaultimpl,link)
  M += '\trm -f package/lib/lib%s.%s\n' % (projectlibrary,link)
  M += '\tln -s lib%s-%s.%s package/lib/lib%s.%s\n' % (projectlibrary,defaultimpl,link,projectlibrary,link)
  M += '\n'
  makefile = M + makefile

# ----- command

copytree('command','build/%s/command'%host)
dirlinksym('build/%s/command'%host,'bin','../package/bin')
dirlinksym('build/%s/command'%host,'lib','../package/lib')
dirlinksym('build/%s/command'%host,'include','../package/include')

with open('build/%s/command/link' % host,'w') as f:
  f.write('#!/bin/sh\n')
  f.write('target="$1"; shift\n')
  f.write('%s \\\n' % firstcompiler)
  f.write('  -fvisibility=hidden \\\n')
  f.write('  -o "$target" "$@"\n')
os.chmod('build/%s/command/link' % host,0o755)

commands = []

for fn in sorted(os.listdir('command')):
  if not fn.endswith('.c'): continue

  base = fn[:-2]
  deps = 'command/%s.o'%base
  link = 'cd command && ./link bin/%s %s.o'%(base,base)
  syslink = ''

  with open('command/%s' % fn) as f:
    for line in f:
      line = line.strip().split()
      if len(line) < 1: continue
      if line[0] != '#include': continue
      if '-lm' in line:
        syslink += ' -lm'
      if '-l%s'%projectlibrary in line:
        deps += ' package/lib/lib%s.so' % projectlibrary
        link += ' lib/lib%s.so' % projectlibrary

  link += syslink

  M = 'command/%s.o: command/%s.c\n' % (base,base)
  M += '\tscripts/compiledefault command %s c -I include\n' % base
  M += '\n'
  makefile = M + makefile
  M = 'package/bin/%s: %s\n' % (base,deps)
  M += '\t%s\n' % link
  M += '\n'
  makefile = M + makefile
  commands += ['package/bin/%s' % base]

M = 'commands: %s\n' % ' '.join(commands)
M += '\n'
makefile = M + makefile

# ----- manual pages

for manpage in sorted(os.listdir('doc/man')):
  section = 'man%s' % manpage[-1]
  targetdir = 'build/%s/package/man/%s' % (host,section)
  os.makedirs(targetdir,exist_ok=True)
  shutil.copy2('doc/man/%s'%manpage,'%s/%s'%(targetdir,manpage))

# ----- make install

M = 'install: scripts/install default\n'
M += '\tscripts/install %s\n' % prefix
M += '\n'
makefile = M + makefile

# ----- make default

M = 'default: \\\n'
for impl in sorted(objects):
  M += 'package/lib/lib%s-%s.a \\\n' % (projectlibrary,impl)
  M += 'package/lib/lib%s-%s.so \\\n' % (projectlibrary,impl)
  M += 'package/lib/lib%s-%s.so.1 \\\n' % (projectlibrary,impl)
M += 'package/lib/lib%s.a \\\n' % projectlibrary
M += 'package/lib/lib%s.so \\\n' % projectlibrary
M += 'package/lib/lib%s.so.1 \\\n' % projectlibrary
M += 'commands\n'
M += '\n'
makefile = M + makefile

with open('build/%s/Makefile' % host,'w') as f:
  f.write(makefile)

# ----- build/0, build/Makefile

dirlinksym('build','0',host)

with open('build/Makefile','w') as f:
  f.write('default:\n')
  f.write('\tcd %s && $(MAKE)\n' % host)
  f.write('\n')
  f.write('install:\n')
  f.write('\tcd %s && $(MAKE) install\n' % host)
  f.write('\n')
  f.write('clean:\n')
  f.write('\trm -r %s\n' % host)

# ----- finish

log('configure done')
with open('build/%s/configure.log'%host,'a') as f:
  f.write(configurelog)
with open('build/%s/configure.log.latest'%host,'w') as f:
  f.write(configurelog)
