// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:io';

import 'package:args/command_runner.dart';
import 'package:mustache4dart/mustache4dart.dart' as mustache;
import 'package:path/path.dart' as path;

import '../android/android.dart' as android;
import '../artifacts.dart';
import '../base/globals.dart';
import '../base/process.dart';

class CreateCommand extends Command {
  final String name = 'create';
  final String description = 'Create a new Flutter project.';

  CreateCommand() {
    argParser.addOption('out', abbr: 'o', help: 'The output directory.');
    argParser.addFlag('pub',
        defaultsTo: true,
        help: 'Whether to run "pub get" after the project has been created.');
  }

  @override
  Future<int> run() async {
    if (!argResults.wasParsed('out')) {
      printStatus('No option specified for the output directory.');
      printStatus(argParser.usage);
      return 2;
    }

    if (ArtifactStore.flutterRoot == null) {
      printError('Neither the --flutter-root command line flag nor the FLUTTER_ROOT environment');
      printError('variable was specified. Unable to find package:flutter.');
      return 2;
    }
    String flutterRoot = path.absolute(ArtifactStore.flutterRoot);

    String flutterPackagePath = path.join(flutterRoot, 'packages', 'flutter');
    if (!FileSystemEntity.isFileSync(path.join(flutterPackagePath, 'pubspec.yaml'))) {
      printError('Unable to find package:flutter in $flutterPackagePath');
      return 2;
    }

    Directory out = new Directory(argResults['out']);

    new FlutterSimpleTemplate().generateInto(out, flutterPackagePath);

    printStatus('');

    String message = '''
All done! To run your application:

  \$ cd ${out.path}
  \$ flutter start
''';

    if (argResults['pub']) {
      int code = await pubGet(directory: out.path);
      if (code != 0)
        return code;
    }

    printStatus('');
    printStatus(message);
    return 0;
  }

  Future<int> pubGet({
    String directory: '',
    bool skipIfAbsent: false
  }) async {
    File pubSpecYaml = new File(path.join(directory, 'pubspec.yaml'));
    File pubSpecLock = new File(path.join(directory, 'pubspec.lock'));
    File dotPackages = new File(path.join(directory, '.packages'));

    if (!pubSpecYaml.existsSync()) {
      if (skipIfAbsent)
        return 0;
      printError('$directory: no pubspec.yaml found');
      return 1;
    }

    if (!pubSpecLock.existsSync() || pubSpecYaml.lastModifiedSync().isAfter(pubSpecLock.lastModifiedSync())) {
      printStatus("Running 'pub get' in '$directory'...");
      int code = await runCommandAndStreamOutput(
        <String>[sdkBinaryName('pub'), '--verbosity=warning', 'get'],
        workingDirectory: directory
      );
      if (code != 0)
        return code;
    }

    if ((pubSpecLock.existsSync() && pubSpecLock.lastModifiedSync().isAfter(pubSpecYaml.lastModifiedSync())) &&
        (dotPackages.existsSync() && dotPackages.lastModifiedSync().isAfter(pubSpecYaml.lastModifiedSync())))
      return 0;

    printError('$directory: pubspec.yaml, pubspec.lock, and .packages are in an inconsistent state');
    return 1;
  }
}

abstract class Template {
  final String name;
  final String description;

  Map<String, String> files = {};

  Template(this.name, this.description);

  void generateInto(Directory dir, String flutterPackagePath) {
    String dirPath = path.normalize(dir.absolute.path);
    String projectName = _normalizeProjectName(path.basename(dirPath));
    printStatus('Creating ${path.basename(projectName)}...');
    dir.createSync(recursive: true);

    String relativeFlutterPackagePath = path.relative(flutterPackagePath, from: dirPath);

    files.forEach((String filePath, String contents) {
      Map m = {
        'projectName': projectName,
        'description': description,
        'flutterPackagePath': relativeFlutterPackagePath
      };
      contents = mustache.render(contents, m);
      filePath = filePath.replaceAll('/', Platform.pathSeparator);
      File file = new File(path.join(dir.path, filePath));
      file.parent.createSync();
      file.writeAsStringSync(contents);
      printStatus('  ${file.path}');
    });
  }

  String toString() => name;
}

class FlutterSimpleTemplate extends Template {
  FlutterSimpleTemplate() : super('flutter-simple', 'A minimal Flutter project.') {
    files['.analysis_options'] = _analysis_options;
    files['.gitignore'] = _gitignore;
    files['flutter.yaml'] = _flutterYaml;
    files['pubspec.yaml'] = _pubspec;
    files['README.md'] = _readme;
    files['android/AndroidManifest.xml'] = _apkManifest;
    files['lib/main.dart'] = _libMain;
  }
}

String _normalizeProjectName(String name) {
  name = name.replaceAll('-', '_').replaceAll(' ', '_');
  // Strip any extension (like .dart).
  if (name.contains('.'))
    name = name.substring(0, name.indexOf('.'));
  return name;
}

const String _analysis_options = r'''
analyzer:
  exclude:
    - 'ios/build/**'
''';

const String _gitignore = r'''
.DS_Store
.atom/
.idea
.packages
.pub/
build/
packages
pubspec.lock
''';

const String _readme = r'''
# {{projectName}}

{{description}}

## Getting Started

For help getting started with Flutter, view our online
[documentation](http://flutter.io/).
''';

const String _pubspec = r'''
name: {{projectName}}
description: {{description}}
dependencies:
  flutter:
    path: {{flutterPackagePath}}
''';

const String _flutterYaml = r'''
name: {{projectName}}
material-design-icons:
  - name: content/add
''';

const String _libMain = r'''
import 'package:flutter/material.dart';

void main() {
  runApp(
    new MaterialApp(
      title: "Flutter Demo",
      routes: <String, RouteBuilder>{
        '/': (RouteArguments args) => new FlutterDemo()
      }
    )
  );
}

class FlutterDemo extends StatefulComponent {
  @override
  State createState() => new FlutterDemoState();
}

class FlutterDemoState extends State {
  int counter = 0;

  void incrementCounter() {
    setState(() {
      counter++;
    });
  }

  Widget build(BuildContext context) {
    return new Scaffold(
      toolBar: new ToolBar(
        center: new Text("Flutter Demo")
      ),
      body: new Material(
        child: new Center(
          child: new Text("Button tapped $counter times.")
        )
      ),
      floatingActionButton: new FloatingActionButton(
        child: new Icon(
          icon: 'content/add'
        ),
        onPressed: incrementCounter
      )
    );
  }
}
''';

final String _apkManifest = '''
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.{{projectName}}"
    android:versionCode="1"
    android:versionName="0.0.1">

    <uses-sdk android:minSdkVersion="${android.minApiLevel}" android:targetSdkVersion="21" />
    <uses-permission android:name="android.permission.INTERNET"/>

    <application android:name="org.domokit.sky.shell.SkyApplication" android:label="{{projectName}}">
        <activity android:name="org.domokit.sky.shell.SkyActivity"
                  android:launchMode="singleTask"
                  android:theme="@android:style/Theme.Black.NoTitleBar"
                  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
                  android:hardwareAccelerated="true"
                  android:windowSoftInputMode="adjustResize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>
</manifest>
''';